From ffe7497cef405711d28f35bb13e9c9043056f622 Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 26 Mar 2026 15:59:42 -0400 Subject: [PATCH 1/2] [blocked-4507]: Update rust-lightning to use default_value_vec Update to be able to write vectors of HTLCLocator for PaymentForwarded. Note that we still don't expect to receive multiple outgoing HTLCs because trampoline has not yet been enabled, but we lay the groundwork here. --- Cargo.toml | 24 ++++++------ src/event.rs | 105 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 539941677..b55762a4c 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -172,15 +172,15 @@ harness = false #vss-client-ng = { path = "../vss-client" } #vss-client-ng = { git = "https://github.com/lightningdevkit/vss-client", branch = "main" } # -#[patch."https://github.com/lightningdevkit/rust-lightning"] -#lightning = { path = "../rust-lightning/lightning" } -#lightning-types = { path = "../rust-lightning/lightning-types" } -#lightning-invoice = { path = "../rust-lightning/lightning-invoice" } -#lightning-net-tokio = { path = "../rust-lightning/lightning-net-tokio" } -#lightning-persister = { path = "../rust-lightning/lightning-persister" } -#lightning-background-processor = { path = "../rust-lightning/lightning-background-processor" } -#lightning-rapid-gossip-sync = { path = "../rust-lightning/lightning-rapid-gossip-sync" } -#lightning-block-sync = { path = "../rust-lightning/lightning-block-sync" } -#lightning-transaction-sync = { path = "../rust-lightning/lightning-transaction-sync" } -#lightning-liquidity = { path = "../rust-lightning/lightning-liquidity" } -#lightning-macros = { path = "../rust-lightning/lightning-macros" } +[patch."https://github.com/lightningdevkit/rust-lightning"] +lightning = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc", features = ["std"] } +lightning-types = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc" } +lightning-invoice = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc" } +lightning-persister = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc", features = ["tokio"] } +lightning-background-processor = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc" } +lightning-rapid-gossip-sync = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc" } +lightning-block-sync = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } +lightning-liquidity = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc", features = ["std"] } +lightning-macros = { git = "https://github.com/carlakc/rust-lightning", rev = "62fde7a019b2d6a696efb7120be1d548efb031cc" } diff --git a/src/event.rs b/src/event.rs index f06d701bc..461334622 100644 --- a/src/event.rs +++ b/src/event.rs @@ -18,10 +18,9 @@ use lightning::events::bump_transaction::BumpTransactionEvent; #[cfg(not(feature = "uniffi"))] use lightning::events::PaidBolt12Invoice; use lightning::events::{ - ClosureReason, Event as LdkEvent, FundingInfo, PaymentFailureReason, PaymentPurpose, - ReplayEvent, + ClosureReason, Event as LdkEvent, FundingInfo, HTLCLocator as LdkHtlcLocator, + PaymentFailureReason, PaymentPurpose, ReplayEvent, }; -use lightning::impl_writeable_tlv_based_enum; use lightning::ln::channelmanager::PaymentId; use lightning::ln::types::ChannelId; use lightning::routing::gossip::NodeId; @@ -32,6 +31,7 @@ use lightning::util::config::{ use lightning::util::errors::APIError; use lightning::util::persist::KVStore; use lightning::util::ser::{Readable, ReadableArgs, Writeable, Writer}; +use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum}; use lightning_liquidity::lsps2::utils::compute_opening_fee; use lightning_types::payment::{PaymentHash, PaymentPreimage}; @@ -61,6 +61,40 @@ use crate::{ UserChannelId, }; +/// Identifies the channel and counterparty that a HTLC was processed with. +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +pub struct HTLCLocator { + /// The channel that the HTLC was sent or received on. + pub channel_id: ChannelId, + /// The `user_channel_id` for the channel. + /// + /// Will only be `None` for events serialized with LDK Node v0.3.0 or prior, or if the + /// payment was settled via an on-chain transaction. + pub user_channel_id: Option, + /// The node id of the counterparty for this HTLC. + /// + /// This is only `None` for HTLCs received prior to LDK Node v0.5 or for events serialized by + /// versions prior to v0.5. + pub node_id: Option, +} + +impl_writeable_tlv_based!(HTLCLocator, { + (1, channel_id, required), + (3, user_channel_id, option), + (5, node_id, option), +}); + +impl From for HTLCLocator { + fn from(value: LdkHtlcLocator) -> Self { + HTLCLocator { + channel_id: value.channel_id, + user_channel_id: value.user_channel_id.map(|u| UserChannelId(u)), + node_id: value.node_id, + } + } +} + /// An event emitted by [`Node`], which should be handled by the user. /// /// [`Node`]: [`crate::Node`] @@ -128,29 +162,14 @@ pub enum Event { }, /// A payment has been forwarded. PaymentForwarded { - /// The channel id of the incoming channel between the previous node and us. - prev_channel_id: ChannelId, - /// The channel id of the outgoing channel between the next node and us. - next_channel_id: ChannelId, - /// The `user_channel_id` of the incoming channel between the previous node and us. - /// - /// Will only be `None` for events serialized with LDK Node v0.3.0 or prior. - prev_user_channel_id: Option, - /// 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. - next_user_channel_id: Option, - /// The node id of the previous node. - /// - /// This is only `None` for HTLCs received prior to LDK Node v0.5 or for events serialized by - /// versions prior to v0.5. - prev_node_id: Option, - /// The node id of the next node. - /// - /// This is only `None` for HTLCs received prior to LDK Node v0.5 or for events serialized by - /// versions prior to v0.5. - next_node_id: Option, + /// The set of incoming HTLCs that were forwarded to our node. Contains a single HTLC for + /// source-routed payments, and may contain multiple HTLCs when we acted as a trampoline + /// router. + prev_htlcs: Vec, + /// The set of outgoing HTLCs forwarded by our node. Contains a single HTLC for regular + /// source-routed payments, and may contain multiple HTLCs when we acted as a trampoline + /// router. + next_htlcs: Vec, /// 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 @@ -323,16 +342,27 @@ impl_writeable_tlv_based_enum!(Event, (7, custom_records, optional_vec), }, (7, PaymentForwarded) => { - (0, prev_channel_id, required), - (1, prev_node_id, option), - (2, next_channel_id, required), - (3, next_node_id, option), - (4, prev_user_channel_id, option), - (6, next_user_channel_id, option), + // Legacy fields: read from old data, never written. + (0, legacy_prev_channel_id, (legacy, ChannelId, |_| Ok(()), |_: &Event| None::>)), + (1, legacy_prev_node_id, (legacy, PublicKey, |_| Ok(()), |_: &Event| None::>)), + (2, legacy_next_channel_id, (legacy, ChannelId, |_| Ok(()), |_: &Event| None::>)), + (3, legacy_next_node_id, (legacy, PublicKey, |_| Ok(()), |_: &Event| None::>)), + (4, legacy_prev_user_channel_id, (legacy, u128, |_| Ok(()), |_: &Event| None::>)), + (6, legacy_next_user_channel_id, (legacy, u128, |_| Ok(()), |_: &Event| None::>)), (8, total_fee_earned_msat, option), (10, skimmed_fee_msat, option), (12, claim_from_onchain_tx, required), (14, outbound_amount_forwarded_msat, option), + (15, prev_htlcs, (default_value_vec, vec![HTLCLocator { + channel_id: legacy_prev_channel_id.ok_or(lightning::ln::msgs::DecodeError::InvalidValue)?, + user_channel_id: legacy_prev_user_channel_id.map(UserChannelId), + node_id: legacy_prev_node_id, + }])), + (17, next_htlcs, (default_value_vec, vec![HTLCLocator { + channel_id: legacy_next_channel_id.ok_or(lightning::ln::msgs::DecodeError::InvalidValue)?, + user_channel_id: legacy_next_user_channel_id.map(UserChannelId), + node_id: legacy_next_node_id, + }])), }, (8, SplicePending) => { (1, channel_id, required), @@ -1400,9 +1430,6 @@ where // reporting the first HTLC in each vec. debug_assert_eq!(prev_htlcs.len(), 1, "unexpected number of prev_htlcs"); debug_assert_eq!(next_htlcs.len(), 1, "unexpected number of next_htlcs"); - let prev_htlc = prev_htlcs - .first() - .expect("we expect at least one prev_htlc for PaymentForwarded"); let next_htlc = next_htlcs .first() .expect("we expect at least one next_htlc for PaymentForwarded"); @@ -1415,12 +1442,8 @@ where } let event = Event::PaymentForwarded { - prev_channel_id: prev_htlc.channel_id, - next_channel_id: next_htlc.channel_id, - prev_user_channel_id: prev_htlc.user_channel_id.map(UserChannelId), - next_user_channel_id: next_htlc.user_channel_id.map(UserChannelId), - prev_node_id: prev_htlc.node_id, - next_node_id: next_htlc.node_id, + prev_htlcs: prev_htlcs.into_iter().map(HTLCLocator::from).collect(), + next_htlcs: next_htlcs.into_iter().map(HTLCLocator::from).collect(), total_fee_earned_msat, skimmed_fee_msat, claim_from_onchain_tx, From 8baef95c87cbcf6f8b31da59149ab094557b4d7c Mon Sep 17 00:00:00 2001 From: Carla Kirk-Cohen Date: Thu, 26 Mar 2026 16:11:46 -0400 Subject: [PATCH 2/2] Assert that we only have skimmed fees for single htlc forwards --- src/event.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/event.rs b/src/event.rs index 461334622..55e0be26e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1425,20 +1425,24 @@ where } } - // We only allow multiple HTLCs in/out for trampoline forwards, which have not yet - // been fully implemented in LDK, so we do not lose any information by just - // reporting the first HTLC in each vec. - debug_assert_eq!(prev_htlcs.len(), 1, "unexpected number of prev_htlcs"); - debug_assert_eq!(next_htlcs.len(), 1, "unexpected number of next_htlcs"); - let next_htlc = next_htlcs - .first() - .expect("we expect at least one next_htlc for PaymentForwarded"); - + // We only expect multiple next_htlcs when we have a trampoline forward, and we do + // not support JIT channels in combination with trampoline. We're not at risk of + // double-reporting a skimmed fee when we have multiple next_htlcs because we + // expect our skimmed fee to be zero. + if skimmed_fee_msat.is_some() { + debug_assert_eq!( + next_htlcs.len(), + 1, + "unexpected skimmed fee for trampoline forward, fee may be double counted" + ); + } if let Some(liquidity_source) = self.liquidity_source.as_ref() { let skimmed_fee_msat = skimmed_fee_msat.unwrap_or(0); - liquidity_source - .handle_payment_forwarded(Some(next_htlc.channel_id), skimmed_fee_msat) - .await; + for next_htlc in next_htlcs.iter() { + liquidity_source + .handle_payment_forwarded(Some(next_htlc.channel_id), skimmed_fee_msat) + .await; + } } let event = Event::PaymentForwarded {