diff --git a/Cargo.toml b/Cargo.toml index 07cabe33f..f5055e67d 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,17 +39,17 @@ default = [] #lightning-liquidity = { version = "0.2.0", features = ["std"] } #lightning-macros = { version = "0.2.0" } -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["std"] } -lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204" } -lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["std"] } -lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204" } -lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["tokio"] } -lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204" } -lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204" } -lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["rest-client", "rpc-client", "tokio"] } -lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } -lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["std"] } -lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204" } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["std"] } +lightning-types = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a" } +lightning-invoice = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["std"] } +lightning-net-tokio = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a" } +lightning-persister = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["tokio"] } +lightning-background-processor = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a" } +lightning-rapid-gossip-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a" } +lightning-block-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["rest-client", "rpc-client", "tokio"] } +lightning-transaction-sync = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["esplora-async-https", "time", "electrum-rustls-ring"] } +lightning-liquidity = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["std"] } +lightning-macros = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a" } bdk_chain = { version = "0.23.0", default-features = false, features = ["std"] } bdk_esplora = { version = "0.22.0", default-features = false, features = ["async-https-rustls", "tokio"]} @@ -79,13 +79,13 @@ async-trait = { version = "0.1", default-features = false } vss-client = { package = "vss-client-ng", version = "0.5" } prost = { version = "0.11.6", default-features = false} #bitcoin-payment-instructions = { version = "0.6" } -bitcoin-payment-instructions = { git = "https://github.com/jkczyz/bitcoin-payment-instructions", rev = "0138feb7acefb1e49102a6fb46d7b776bf43265e" } +bitcoin-payment-instructions = { git = "https://github.com/carlaKC/bitcoin-payment-instructions", rev = "c22c9b836b70d4c915dd28701e11083a8b558d56" } [target.'cfg(windows)'.dependencies] winapi = { version = "0.3", features = ["winbase"] } [dev-dependencies] -lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "98393b3de3d8aec897e9ab783cb2418da504e204", features = ["std", "_test_utils"] } +lightning = { git = "https://github.com/lightningdevkit/rust-lightning", rev = "128ead25e8d62b0262b71711631220983c48756a", features = ["std", "_test_utils"] } rand = { version = "0.9.2", default-features = false, features = ["std", "thread_rng", "os_rng"] } proptest = "1.0.0" regex = "1.5.6" @@ -171,15 +171,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 = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-types = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-invoice = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-net-tokio = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-persister = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-background-processor = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-rapid-gossip-sync = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-block-sync = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-transaction-sync = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-liquidity = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } +lightning-macros = { git = "https://github.com/carlakc/rust-lightning", rev = "ee1af46e20adae3f764bc7c490f55697bff036d6" } diff --git a/src/event.rs b/src/event.rs index c4949a5ac..a59dfbcf2 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,50 @@ 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, + } + } +} + +impl From for LdkHTLCLocator { + fn from(value: HTLCLocator) -> Self { + LdkHTLCLocator { + channel_id: value.channel_id, + user_channel_id: value.user_channel_id.map(|u| u.0), + node_id: value.node_id, + } + } +} + /// An event emitted by [`Node`], which should be handled by the user. /// /// [`Node`]: [`crate::Node`] @@ -128,29 +172,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 +352,31 @@ 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), + // We don't write our legacy types because we don't need to support downgrades, but we do + // read them so that we can forwards compatibly fill prev/next_htlcs on upgrade. + (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), + // We cannot implement Readable/Writeable for Vec because we do not own the + // trait or the type (Vec). To work around this, and prevent duplicating serialization code, + // we map to the underlying LdkHTLCLocator type for serialization. + (15, prev_htlcs, (default_value_vec, vec![HTLCLocator { + channel_id: legacy_prev_channel_id.ok_or(DecodeError::InvalidValue)?, + user_channel_id: legacy_prev_user_channel_id.map(|v| UserChannelId(v)), + node_id: legacy_prev_node_id, + }])), + (17, next_htlcs, (default_value_vec, vec![HTLCLocator { + channel_id: legacy_next_channel_id.ok_or(DecodeError::InvalidValue)?, + user_channel_id: legacy_next_user_channel_id.map(|v| UserChannelId(v)), + node_id: legacy_next_node_id, + }])), }, (8, SplicePending) => { (1, channel_id, required), @@ -644,7 +688,8 @@ where ) .unwrap_or_else(|e| { log_error!(self.logger, "Failed to force close channel after funding generation failed: {:?}", e); - debug_assert!(false, + debug_assert!( + false, "Failed to force close channel after funding generation failed" ); }); @@ -1306,12 +1351,8 @@ where } }, LdkEvent::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, @@ -1322,11 +1363,10 @@ where let nodes = read_only_network_graph.nodes(); let channels = self.channel_manager.list_channels(); - let node_str = |channel_id: &Option| { - channel_id - .and_then(|channel_id| { - channels.iter().find(|c| c.channel_id == channel_id) - }) + let node_str = |channel_id: &ChannelId| { + channels + .iter() + .find(|c| c.channel_id == *channel_id) .and_then(|channel| { nodes.get(&NodeId::from_pubkey(&channel.counterparty.node_id)) }) @@ -1338,21 +1378,21 @@ where }) }) }; - let channel_str = |channel_id: &Option| { - channel_id - .map(|channel_id| format!(" with channel {}", channel_id)) - .unwrap_or_default() - }; - let from_prev_str = format!( - " from {}{}", - node_str(&prev_channel_id), - channel_str(&prev_channel_id) - ); - let to_next_str = format!( - " to {}{}", - node_str(&next_channel_id), - channel_str(&next_channel_id) - ); + let from_prev_str: String = prev_htlcs + .iter() + .map(|htlc| { + format!("with {} on {}", node_str(&htlc.channel_id), htlc.channel_id) + }) + .collect::>() + .join(", "); + + let to_next_str: String = next_htlcs + .iter() + .map(|htlc| { + format!("with {} on {}", node_str(&htlc.channel_id), htlc.channel_id) + }) + .collect::>() + .join(", "); let fee_earned = total_fee_earned_msat.unwrap_or(0); if claim_from_onchain_tx { @@ -1367,8 +1407,10 @@ where } else { log_info!( self.logger, - "Forwarded payment{}{} of {}msat, earning {}msat in fees.", + "Forwarded payment with {} inbound HTLC(s) ({}) and {} outbound HTLC(s) ({}) of {}msat, earning {}msat in fees.", + prev_htlcs.len(), from_prev_str, + next_htlcs.len(), to_next_str, outbound_amount_forwarded_msat.unwrap_or(0), fee_earned, @@ -1378,18 +1420,16 @@ where if let Some(liquidity_source) = self.liquidity_source.as_ref() { let skimmed_fee_msat = skimmed_fee_msat.unwrap_or(0); + let next_channel_ids: Vec = + next_htlcs.iter().map(|htlc| htlc.channel_id).collect(); liquidity_source - .handle_payment_forwarded(next_channel_id, skimmed_fee_msat) + .handle_payment_forwarded(&next_channel_ids, skimmed_fee_msat) .await; } let event = Event::PaymentForwarded { - prev_channel_id: prev_channel_id.expect("prev_channel_id expected for events generated by LDK versions greater than 0.0.107."), - next_channel_id: next_channel_id.expect("next_channel_id expected for events generated by LDK versions greater than 0.0.107."), - prev_user_channel_id: prev_user_channel_id.map(UserChannelId), - next_user_channel_id: next_user_channel_id.map(UserChannelId), - prev_node_id, - next_node_id, + prev_htlcs: prev_htlcs.into_iter().map(|h| h.into()).collect(), + next_htlcs: next_htlcs.into_iter().map(|h| h.into()).collect(), total_fee_earned_msat, skimmed_fee_msat, claim_from_onchain_tx, diff --git a/src/liquidity.rs b/src/liquidity.rs index ee9863eb6..df47c5cc5 100644 --- a/src/liquidity.rs +++ b/src/liquidity.rs @@ -1386,19 +1386,22 @@ where } pub(crate) async fn handle_payment_forwarded( - &self, next_channel_id: Option, skimmed_fee_msat: u64, + &self, next_channel_ids: &[ChannelId], skimmed_fee_msat: u64, ) { - if let Some(next_channel_id) = next_channel_id { - if let Some(lsps2_service_handler) = self.liquidity_manager.lsps2_service_handler() { - if let Err(e) = - lsps2_service_handler.payment_forwarded(next_channel_id, skimmed_fee_msat).await - { - log_error!( - self.logger, - "LSPS2 service failed to handle PaymentForwarded: {:?}", - e - ); - } + if next_channel_ids.len() != 1 { + log_error!( + self.logger, + "Expected exactly one next channel for LSPS2 payment forwarded, got {}", + next_channel_ids.len() + ); + return; + } + + if let Some(lsps2_service_handler) = self.liquidity_manager.lsps2_service_handler() { + if let Err(e) = + lsps2_service_handler.payment_forwarded(next_channel_ids[0], skimmed_fee_msat).await + { + log_error!(self.logger, "LSPS2 service failed to handle PaymentForwarded: {:?}", e); } } }