diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index fd5e5d15b9f..5e734b8e5f7 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -739,10 +739,6 @@ impl_writeable_tlv_based_enum!(SentHTLCId, }, ); -// (src_outbound_scid_alias, src_counterparty_node_id, src_funding_outpoint, src_chan_id, src_user_chan_id) -type PerSourcePendingForward = - (u64, PublicKey, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>); - type FailedHTLCForward = (HTLCSource, PaymentHash, HTLCFailReason, HTLCHandlingFailureType); mod fuzzy_channelmanager { @@ -4771,7 +4767,9 @@ where } } - fn forward_needs_intercept_to_known_chan(&self, outbound_chan: &FundedChannel) -> bool { + fn forward_needs_intercept_to_known_chan( + &self, prev_chan_public: bool, outbound_chan: &FundedChannel, + ) -> bool { let intercept_flags = self.config.read().unwrap().htlc_interception_flags; if !outbound_chan.context.should_announce() { if outbound_chan.context.is_connected() { @@ -4788,6 +4786,23 @@ where return true; } } + if prev_chan_public { + if outbound_chan.context.should_announce() { + if intercept_flags & (HTLCInterceptionFlags::FromPublicToPublicChannels as u8) != 0 + { + return true; + } + } else { + if intercept_flags & (HTLCInterceptionFlags::FromPublicToPrivateChannels as u8) != 0 + { + return true; + } + } + } else { + if intercept_flags & (HTLCInterceptionFlags::FromPrivateChannels as u8) != 0 { + return true; + } + } false } @@ -4881,7 +4896,7 @@ where } fn can_forward_htlc_should_intercept( - &self, msg: &msgs::UpdateAddHTLC, next_hop: &NextPacketDetails, + &self, msg: &msgs::UpdateAddHTLC, prev_chan_public: bool, next_hop: &NextPacketDetails, ) -> Result { let outgoing_scid = match next_hop.outgoing_connector { HopConnector::ShortChannelId(scid) => scid, @@ -4900,7 +4915,7 @@ where // times we do it. let intercept = match self.do_funded_channel_callback(outgoing_scid, |chan: &mut FundedChannel| { - let intercept = self.forward_needs_intercept_to_known_chan(chan); + let intercept = self.forward_needs_intercept_to_known_chan(prev_chan_public, chan); self.can_forward_htlc_to_outgoing_channel(chan, msg, next_hop, intercept)?; Ok(intercept) }) { @@ -6771,15 +6786,16 @@ where ..payment.forward_info }; - let mut per_source_pending_forward = [( - payment.prev_outbound_scid_alias, - payment.prev_counterparty_node_id, - payment.prev_funding_outpoint, - payment.prev_channel_id, - payment.prev_user_channel_id, - vec![(pending_htlc_info, payment.prev_htlc_id)], - )]; - self.forward_htlcs(&mut per_source_pending_forward); + let forward = [PendingAddHTLCInfo { + prev_outbound_scid_alias: payment.prev_outbound_scid_alias, + prev_htlc_id: payment.prev_htlc_id, + prev_counterparty_node_id: payment.prev_counterparty_node_id, + prev_channel_id: payment.prev_channel_id, + prev_funding_outpoint: payment.prev_funding_outpoint, + prev_user_channel_id: payment.prev_user_channel_id, + forward_info: pending_htlc_info, + }]; + self.forward_htlcs(forward); Ok(()) } @@ -6848,17 +6864,13 @@ where let incoming_channel_details_opt = self.do_funded_channel_callback( incoming_scid_alias, |chan: &mut FundedChannel| { - let counterparty_node_id = chan.context.get_counterparty_node_id(); - let channel_id = chan.context.channel_id(); - let funding_txo = chan.funding.get_funding_txo().unwrap(); - let user_channel_id = chan.context.get_user_id(); - let accept_underpaying_htlcs = chan.context.config().accept_underpaying_htlcs; ( - counterparty_node_id, - channel_id, - funding_txo, - user_channel_id, - accept_underpaying_htlcs, + chan.context.get_counterparty_node_id(), + chan.context.channel_id(), + chan.funding.get_funding_txo().unwrap(), + chan.context.get_user_id(), + chan.context.config().accept_underpaying_htlcs, + chan.context.should_announce(), ) }, ); @@ -6868,6 +6880,7 @@ where incoming_funding_txo, incoming_user_channel_id, incoming_accept_underpaying_htlcs, + incoming_chan_is_public, ) = if let Some(incoming_channel_details) = incoming_channel_details_opt { incoming_channel_details } else { @@ -6992,9 +7005,11 @@ where // Now process the HTLC on the outgoing channel if it's a forward. let mut intercept_forward = false; if let Some(next_packet_details) = next_packet_details_opt.as_ref() { - match self - .can_forward_htlc_should_intercept(&update_add_htlc, next_packet_details) - { + match self.can_forward_htlc_should_intercept( + &update_add_htlc, + incoming_chan_is_public, + next_packet_details, + ) { Err(reason) => { fail_htlc_continue_to_next!(reason); }, @@ -7010,7 +7025,7 @@ where next_packet_details_opt.map(|d| d.next_packet_pubkey), ) { Ok(info) => { - let to_pending_add = |info| PendingAddHTLCInfo { + let pending_add = PendingAddHTLCInfo { prev_outbound_scid_alias: incoming_scid_alias, prev_counterparty_node_id: incoming_counterparty_node_id, prev_funding_outpoint: incoming_funding_txo, @@ -7032,7 +7047,7 @@ where Some(incoming_channel_id), Some(update_add_htlc.payment_hash), ); - if info.routing.should_hold_htlc() { + if pending_add.forward_info.routing.should_hold_htlc() { let mut held_htlcs = self.pending_intercepted_htlcs.lock().unwrap(); let intercept_id = intercept_id(); match held_htlcs.entry(intercept_id) { @@ -7041,7 +7056,6 @@ where logger, "Intercepted held HTLC with id {intercept_id}, holding until the recipient is online" ); - let pending_add = to_pending_add(info); entry.insert(pending_add); }, hash_map::Entry::Occupied(_) => { @@ -7058,7 +7072,6 @@ where self.pending_intercepted_htlcs.lock().unwrap(); match pending_intercepts.entry(intercept_id) { hash_map::Entry::Vacant(entry) => { - let pending_add = to_pending_add(info); if let Ok(intercept_ev) = create_htlc_intercepted_event(intercept_id, &pending_add) { @@ -7098,7 +7111,7 @@ where }, } } else { - htlc_forwards.push((info, update_add_htlc.htlc_id)) + htlc_forwards.push(pending_add); } }, Err(inbound_err) => { @@ -7118,15 +7131,7 @@ where // Process all of the forwards and failures for the channel in which the HTLCs were // proposed to as a batch. - let pending_forwards = ( - incoming_scid_alias, - incoming_counterparty_node_id, - incoming_funding_txo, - incoming_channel_id, - incoming_user_channel_id, - htlc_forwards, - ); - self.forward_htlcs(&mut [pending_forwards]); + self.forward_htlcs(htlc_forwards); for (htlc_fail, failure_type, failure_reason) in htlc_fails.drain(..) { let failure = match htlc_fail { HTLCFailureMsg::Relay(fail_htlc) => HTLCForwardInfo::FailHTLC { @@ -7220,7 +7225,7 @@ where let mut new_events = VecDeque::new(); let mut failed_forwards = Vec::new(); - let mut phantom_receives: Vec = Vec::new(); + let mut phantom_receives: Vec = Vec::new(); let mut forward_htlcs = new_hash_map(); mem::swap(&mut forward_htlcs, &mut self.forward_htlcs.lock().unwrap()); @@ -7266,7 +7271,7 @@ where None, ); } - self.forward_htlcs(&mut phantom_receives); + self.forward_htlcs(phantom_receives); if self.check_free_holding_cells() { should_persist = NotifyOption::DoPersist; @@ -7286,7 +7291,7 @@ where fn forwarding_channel_not_found( &self, forward_infos: impl Iterator, short_chan_id: u64, forwarding_counterparty: Option, failed_forwards: &mut Vec, - phantom_receives: &mut Vec, + phantom_receives: &mut Vec, ) { for forward_info in forward_infos { match forward_info { @@ -7408,14 +7413,15 @@ where current_height, ); match create_res { - Ok(info) => phantom_receives.push(( + Ok(info) => phantom_receives.push(PendingAddHTLCInfo { + forward_info: info, prev_outbound_scid_alias, + prev_htlc_id, prev_counterparty_node_id, - prev_funding_outpoint, prev_channel_id, + prev_funding_outpoint, prev_user_channel_id, - vec![(info, prev_htlc_id)], - )), + }), Err(InboundHTLCErr { reason, err_data, msg }) => { failure_handler( msg, @@ -7467,7 +7473,7 @@ where fn process_forward_htlcs( &self, short_chan_id: u64, pending_forwards: &mut Vec, failed_forwards: &mut Vec, - phantom_receives: &mut Vec, + phantom_receives: &mut Vec, ) { let mut forwarding_counterparty = None; @@ -9503,8 +9509,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ fn post_monitor_update_unlock( &self, channel_id: ChannelId, counterparty_node_id: PublicKey, unbroadcasted_batch_funding_txid: Option, - update_actions: Vec, - htlc_forwards: Option, + update_actions: Vec, htlc_forwards: Vec, decode_update_add_htlcs: Option<(u64, Vec)>, finalized_claimed_htlcs: Vec<(HTLCSource, Option)>, failed_htlcs: Vec<(HTLCSource, PaymentHash, HTLCFailReason)>, @@ -9559,9 +9564,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ self.handle_monitor_update_completion_actions(update_actions); - if let Some(forwards) = htlc_forwards { - self.forward_htlcs(&mut [forwards][..]); - } + self.forward_htlcs(htlc_forwards); if let Some(decode) = decode_update_add_htlcs { self.push_decode_update_add_htlcs(decode); } @@ -10102,7 +10105,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ channel_ready: Option, announcement_sigs: Option, tx_signatures: Option, tx_abort: Option, channel_ready_order: ChannelReadyOrder, - ) -> (Option<(u64, PublicKey, OutPoint, ChannelId, u128, Vec<(PendingHTLCInfo, u64)>)>, Option<(u64, Vec)>) { + ) -> (Vec, Option<(u64, Vec)>) { let logger = WithChannelContext::from(&self.logger, &channel.context, None); log_trace!(logger, "Handling channel resumption with {} RAA, {} commitment update, {} pending forwards, {} pending update_add_htlcs, {}broadcasting funding, {} channel ready, {} announcement, {} tx_signatures, {} tx_abort", if raa.is_some() { "an" } else { "no" }, @@ -10118,13 +10121,19 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ let counterparty_node_id = channel.context.get_counterparty_node_id(); let outbound_scid_alias = channel.context.outbound_scid_alias(); - let mut htlc_forwards = None; + let mut htlc_forwards = Vec::new(); if !pending_forwards.is_empty() { - htlc_forwards = Some(( - outbound_scid_alias, channel.context.get_counterparty_node_id(), - channel.funding.get_funding_txo().unwrap(), channel.context.channel_id(), - channel.context.get_user_id(), pending_forwards - )); + htlc_forwards = pending_forwards.into_iter().map(|(forward_info, prev_htlc_id)| { + PendingAddHTLCInfo { + forward_info, + prev_outbound_scid_alias: outbound_scid_alias, + prev_htlc_id, + prev_counterparty_node_id: channel.context.get_counterparty_node_id(), + prev_channel_id: channel.context.channel_id(), + prev_funding_outpoint: channel.funding.get_funding_txo().unwrap(), + prev_user_channel_id: channel.context.get_user_id(), + } + }).collect(); } let mut decode_update_add_htlcs = None; if !pending_update_adds.is_empty() { @@ -11979,44 +11988,22 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ } #[inline] - fn forward_htlcs(&self, per_source_pending_forwards: &mut [PerSourcePendingForward]) { - for &mut ( - prev_outbound_scid_alias, - prev_counterparty_node_id, - prev_funding_outpoint, - prev_channel_id, - prev_user_channel_id, - ref mut pending_forwards, - ) in per_source_pending_forwards - { - if !pending_forwards.is_empty() { - for (forward_info, prev_htlc_id) in pending_forwards.drain(..) { - let scid = match forward_info.routing { - PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id, - PendingHTLCRouting::TrampolineForward { .. } - | PendingHTLCRouting::Receive { .. } - | PendingHTLCRouting::ReceiveKeysend { .. } => 0, - }; - - let pending_add = PendingAddHTLCInfo { - prev_outbound_scid_alias, - prev_counterparty_node_id, - prev_funding_outpoint, - prev_channel_id, - prev_htlc_id, - prev_user_channel_id, - forward_info, - }; + fn forward_htlcs>(&self, pending_forwards: I) { + for htlc in pending_forwards.into_iter() { + let scid = match htlc.forward_info.routing { + PendingHTLCRouting::Forward { short_channel_id, .. } => short_channel_id, + PendingHTLCRouting::TrampolineForward { .. } + | PendingHTLCRouting::Receive { .. } + | PendingHTLCRouting::ReceiveKeysend { .. } => 0, + }; - match self.forward_htlcs.lock().unwrap().entry(scid) { - hash_map::Entry::Occupied(mut entry) => { - entry.get_mut().push(HTLCForwardInfo::AddHTLC(pending_add)); - }, - hash_map::Entry::Vacant(entry) => { - entry.insert(vec![HTLCForwardInfo::AddHTLC(pending_add)]); - }, - } - } + match self.forward_htlcs.lock().unwrap().entry(scid) { + hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().push(HTLCForwardInfo::AddHTLC(htlc)); + }, + hash_map::Entry::Vacant(entry) => { + entry.insert(vec![HTLCForwardInfo::AddHTLC(htlc)]); + }, } } } @@ -12354,7 +12341,7 @@ This indicates a bug inside LDK. Please report this error at https://github.com/ Vec::new(), Vec::new(), None, responses.channel_ready, responses.announcement_sigs, responses.tx_signatures, responses.tx_abort, responses.channel_ready_order, ); - debug_assert!(htlc_forwards.is_none()); + debug_assert!(htlc_forwards.is_empty()); debug_assert!(decode_update_add_htlcs.is_none()); if let Some(upd) = channel_update { peer_state.pending_msg_events.push(upd); @@ -16348,9 +16335,29 @@ where ); log_trace!(logger, "Releasing held htlc with intercept_id {}", intercept_id); + let prev_chan_public = { + let per_peer_state = self.per_peer_state.read().unwrap(); + let peer_state = per_peer_state + .get(&htlc.prev_counterparty_node_id) + .map(|mtx| mtx.lock().unwrap()); + let chan_state = peer_state + .as_ref() + .map(|state| state.channel_by_id.get(&htlc.prev_channel_id)) + .flatten(); + if let Some(chan_state) = chan_state { + chan_state.context().should_announce() + } else { + // If the inbound channel has closed since the HTLC was held, we really + // shouldn't forward it - forwarding it now would result in, at best, + // having to claim the HTLC on chain. Instead, drop the HTLC and let the + // counterparty claim their money on chain. + return; + } + }; + let should_intercept = self .do_funded_channel_callback(next_hop_scid, |chan| { - self.forward_needs_intercept_to_known_chan(chan) + self.forward_needs_intercept_to_known_chan(prev_chan_public, chan) }) .unwrap_or_else(|| self.forward_needs_intercept_to_unknown_chan(next_hop_scid)); @@ -16388,15 +16395,7 @@ where }, } } else { - let mut per_source_pending_forward = [( - htlc.prev_outbound_scid_alias, - htlc.prev_counterparty_node_id, - htlc.prev_funding_outpoint, - htlc.prev_channel_id, - htlc.prev_user_channel_id, - vec![(htlc.forward_info, htlc.prev_htlc_id)], - )]; - self.forward_htlcs(&mut per_source_pending_forward); + self.forward_htlcs([htlc]); } }, _ => return, diff --git a/lightning/src/ln/interception_tests.rs b/lightning/src/ln/interception_tests.rs index 11b5de166f6..2122e86a3e0 100644 --- a/lightning/src/ln/interception_tests.rs +++ b/lightning/src/ln/interception_tests.rs @@ -50,7 +50,16 @@ fn do_test_htlc_interception_flags( let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, Some(intercept_config), None]); let nodes = create_network(3, &node_cfgs, &node_chanmgrs); - create_announced_chan_between_nodes(&nodes, 0, 1); + let inbound_private = match flag { + Flag::FromPrivateChannels => { + create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 100000, 0); + true + }, + _ => { + create_announced_chan_between_nodes(&nodes, 0, 1); + false + }, + }; let node_0_id = nodes[0].node.get_our_node_id(); let node_1_id = nodes[1].node.get_our_node_id(); @@ -58,29 +67,31 @@ fn do_test_htlc_interception_flags( // First open the right type of channel (and get it in the right state) for the bit we're // testing. - let (target_scid, target_chan_id) = match flag { - Flag::ToOfflinePrivateChannels | Flag::ToOnlinePrivateChannels => { + let (target_scid, target_chan_id, outbound_private_for_known_scids) = match flag { + Flag::ToOfflinePrivateChannels + | Flag::ToOnlinePrivateChannels + | Flag::FromPublicToPrivateChannels => { create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 100000, 0); let chan_id = nodes[2].node.list_channels()[0].channel_id; let scid = nodes[2].node.list_channels()[0].short_channel_id.unwrap(); if flag == Flag::ToOfflinePrivateChannels { nodes[1].node.peer_disconnected(node_2_id); nodes[2].node.peer_disconnected(node_1_id); - } else { - assert_eq!(flag, Flag::ToOnlinePrivateChannels); } - (scid, chan_id) + (scid, chan_id, Some(true)) }, - Flag::ToInterceptSCIDs | Flag::ToPublicChannels | Flag::ToUnknownSCIDs => { + Flag::ToInterceptSCIDs + | Flag::ToPublicChannels + | Flag::FromPrivateChannels + | Flag::FromPublicToPublicChannels + | Flag::ToUnknownSCIDs => { let (chan_upd, _, chan_id, _) = create_announced_chan_between_nodes(&nodes, 1, 2); if flag == Flag::ToInterceptSCIDs { - (nodes[1].node.get_intercept_scid(), chan_id) - } else if flag == Flag::ToPublicChannels { - (chan_upd.contents.short_channel_id, chan_id) + (nodes[1].node.get_intercept_scid(), chan_id, None) } else if flag == Flag::ToUnknownSCIDs { - (42424242, chan_id) + (42424242, chan_id, None) } else { - panic!(); + (chan_upd.contents.short_channel_id, chan_id, Some(false)) } }, _ => panic!("Combined flags aren't allowed"), @@ -100,19 +111,51 @@ fn do_test_htlc_interception_flags( get_route_and_payment_hash!(nodes[0], nodes[2], pay_params, amt_msat); route.paths[0].hops[1].short_channel_id = target_scid; - let interception_bit_match = (flags_bitmask & (flag as u8)) != 0; + let mut should_intercept = false; + for a_flag in ALL_FLAGS { + if flags_bitmask & (a_flag as u8) != 0 { + match a_flag { + Flag::ToInterceptSCIDs => { + should_intercept |= flag == Flag::ToInterceptSCIDs; + }, + Flag::ToOfflinePrivateChannels => { + should_intercept |= flag == Flag::ToOfflinePrivateChannels; + }, + Flag::ToOnlinePrivateChannels => { + should_intercept |= flag != Flag::ToOfflinePrivateChannels + && outbound_private_for_known_scids == Some(true); + }, + Flag::ToPublicChannels => { + should_intercept |= outbound_private_for_known_scids == Some(false); + }, + Flag::ToUnknownSCIDs => { + should_intercept |= flag == Flag::ToUnknownSCIDs; + }, + Flag::FromPrivateChannels => { + should_intercept |= inbound_private; + }, + Flag::FromPublicToPrivateChannels => { + should_intercept |= + !inbound_private && outbound_private_for_known_scids == Some(true); + }, + Flag::FromPublicToPublicChannels => { + should_intercept |= + !inbound_private && outbound_private_for_known_scids == Some(false); + }, + _ => panic!("Combined flags aren't allowed"), + } + } + } + match modification { Some(ForwardingMod::FeeTooLow) => { - assert!( - interception_bit_match, - "No reason to test failing if we aren't trying to intercept", - ); + assert!(should_intercept, "No reason to test failing if we aren't trying to intercept"); route.paths[0].hops[0].fee_msat = 500; }, Some(ForwardingMod::CLTVBelowConfig) => { route.paths[0].hops[0].cltv_expiry_delta = 6 * 12; assert!( - interception_bit_match, + should_intercept, "No reason to test failing if we aren't trying to intercept", ); }, @@ -132,7 +175,7 @@ fn do_test_htlc_interception_flags( do_commitment_signed_dance(&nodes[1], &nodes[0], &payment_event.commitment_msg, false, true); expect_and_process_pending_htlcs(&nodes[1], false); - if interception_bit_match && modification.is_none() { + if should_intercept && modification.is_none() { // If we were set to intercept, check that we got an interception event then // forward the HTLC on to nodes[2] and claim the payment. let intercept_id; @@ -171,7 +214,14 @@ fn do_test_htlc_interception_flags( // If we were not set to intercept, check that the HTLC either failed or was // automatically forwarded as appropriate. match (modification, flag) { - (None, Flag::ToOnlinePrivateChannels | Flag::ToPublicChannels) => { + ( + None, + Flag::ToOnlinePrivateChannels + | Flag::ToPublicChannels + | Flag::FromPrivateChannels + | Flag::FromPublicToPrivateChannels + | Flag::FromPublicToPublicChannels, + ) => { check_added_monitors(&nodes[1], 1); let forward_ev = SendEvent::from_node(&nodes[1]); @@ -240,31 +290,55 @@ fn do_test_htlc_interception_flags( } const MAX_BITMASK: u8 = HTLCInterceptionFlags::AllValidHTLCs as u8; -const ALL_FLAGS: [HTLCInterceptionFlags; 5] = [ +const ALL_FLAGS: [HTLCInterceptionFlags; 8] = [ HTLCInterceptionFlags::ToInterceptSCIDs, HTLCInterceptionFlags::ToOfflinePrivateChannels, HTLCInterceptionFlags::ToOnlinePrivateChannels, HTLCInterceptionFlags::ToPublicChannels, HTLCInterceptionFlags::ToUnknownSCIDs, + HTLCInterceptionFlags::FromPrivateChannels, + HTLCInterceptionFlags::FromPublicToPrivateChannels, + HTLCInterceptionFlags::FromPublicToPublicChannels, ]; - #[test] -fn test_htlc_interception_flags() { +fn check_all_flags() { let mut all_flag_bits = 0; for flag in ALL_FLAGS { all_flag_bits |= flag as isize; } assert_eq!(all_flag_bits, MAX_BITMASK as isize, "all flags must test all bits"); +} +fn test_htlc_interception_flags_subrange>(r: I) { // Test all 2^5 = 32 combinations of the HTLCInterceptionFlags bitmask // For each combination, test 5 different HTLC forwards and verify correct interception behavior - for flags_bitmask in 0..=MAX_BITMASK { + for flags_bitmask in r { for flag in ALL_FLAGS { do_test_htlc_interception_flags(flags_bitmask, flag, None); } } } +#[test] +fn test_htlc_interception_flags_a() { + test_htlc_interception_flags_subrange(0..MAX_BITMASK / 4); +} + +#[test] +fn test_htlc_interception_flags_b() { + test_htlc_interception_flags_subrange(MAX_BITMASK / 4..MAX_BITMASK / 2); +} + +#[test] +fn test_htlc_interception_flags_c() { + test_htlc_interception_flags_subrange(MAX_BITMASK / 2..MAX_BITMASK / 4 * 3); +} + +#[test] +fn test_htlc_interception_flags_d() { + test_htlc_interception_flags_subrange(MAX_BITMASK / 4 * 3..=MAX_BITMASK); +} + #[test] fn test_htlc_bad_for_chan_config() { // Test that interception won't be done if an HTLC fails to meet the target channel's channel @@ -273,6 +347,9 @@ fn test_htlc_bad_for_chan_config() { HTLCInterceptionFlags::ToOfflinePrivateChannels, HTLCInterceptionFlags::ToOnlinePrivateChannels, HTLCInterceptionFlags::ToPublicChannels, + HTLCInterceptionFlags::FromPrivateChannels, + HTLCInterceptionFlags::FromPublicToPrivateChannels, + HTLCInterceptionFlags::FromPublicToPublicChannels, ]; for flag in have_chan_flags { do_test_htlc_interception_flags(flag as u8, flag, Some(ForwardingMod::FeeTooLow)); diff --git a/lightning/src/util/config.rs b/lightning/src/util/config.rs index feb326cfad6..aa9dd667204 100644 --- a/lightning/src/util/config.rs +++ b/lightning/src/util/config.rs @@ -930,6 +930,51 @@ pub enum HTLCInterceptionFlags { | Self::ToOfflinePrivateChannels as isize | Self::ToOnlinePrivateChannels as isize | Self::ToPublicChannels as isize, + /// If this flag is set, any attempts to forward a payment from a private channel (to anywhere) + /// will instead generate an [`Event::HTLCIntercepted`] which must be handled the same as any + /// other intercepted HTLC. + /// + /// This is useful for an LSP that may wish to apply a higher fee policy on their channels when + /// the HTLC comes from a private channel client. Note that HTLCs which do not pay the + /// configured fee rate or do not meet the [`ChannelConfig::cltv_expiry_delta`] will fail. + /// Thus, this cannot be used to allow forwarding for less than the public fees. + /// + /// Note that no HTLCs to unknown channels will be intercepted by this flag. For that, use + /// [`Self::ToUnknownSCIDs`]. + /// + /// [`Event::HTLCIntercepted`]: crate::events::Event::HTLCIntercepted + FromPrivateChannels = 1 << 4, + /// If this flag is set, any attempts to forward a payment from a public channel to a private + /// channel will instead generate an [`Event::HTLCIntercepted`] which must be handled the same + /// as any other intercepted HTLC. + /// + /// This is useful for an LSP that may wish to take an additional fee on any HTLCs which are + /// forwarded to a private channel client but wishes to avoid taking that fee when forwarding + /// an HTLC from a private channel client to another private channel client. + /// + /// Note that HTLCs which do not pay the configured fee rate or do not meet the + /// [`ChannelConfig::cltv_expiry_delta`] will fail and not be intercepted. + /// + /// Note that no HTLCs to unknown channels will be intercepted by this flag. For that, use + /// [`Self::ToUnknownSCIDs`]. + /// + /// [`Event::HTLCIntercepted`]: crate::events::Event::HTLCIntercepted + FromPublicToPrivateChannels = 1 << 5, + /// If this flag is set, any attempts to forward a payment from a public channel to another + /// public channel will instead generate an [`Event::HTLCIntercepted`] which must be handled + /// the same as any other intercepted HTLC. + /// + /// This primarily exists for completeness, and generally interception of of HTLCs between + /// public channels is *strongly* discouraged. + /// + /// Note that HTLCs which do not pay the configured fee rate or do not meet the + /// [`ChannelConfig::cltv_expiry_delta`] will fail and not be intercepted. + /// + /// Note that no HTLCs to unknown channels will be intercepted by this flag. For that, use + /// [`Self::ToUnknownSCIDs`]. + /// + /// [`Event::HTLCIntercepted`]: crate::events::Event::HTLCIntercepted + FromPublicToPublicChannels = 1 << 6, /// If this flag is set, any attempts to forward a payment to an unknown short channel id will /// instead generate an [`Event::HTLCIntercepted`] which must be handled the same as any other /// intercepted HTLC. @@ -941,7 +986,7 @@ pub enum HTLCInterceptionFlags { /// delta meets your requirements before forwarding the HTLC. /// /// [`Event::HTLCIntercepted`]: crate::events::Event::HTLCIntercepted - ToUnknownSCIDs = 1 << 4, + ToUnknownSCIDs = 1 << 7, /// If these flags are set, all HTLCs being forwarded over this node will instead generate an /// [`Event::HTLCIntercepted`] which must be handled the same as any other intercepted HTLC. /// @@ -951,7 +996,7 @@ pub enum HTLCInterceptionFlags { /// validate the fee and CLTV delta meets your requirements before forwarding the HTLC. /// /// [`Event::HTLCIntercepted`]: crate::events::Event::HTLCIntercepted - AllValidHTLCs = Self::ToAllKnownSCIDs as isize | Self::ToUnknownSCIDs as isize, + AllValidHTLCs = 0xff, } impl Into for HTLCInterceptionFlags {