From c1f1b78ea6fcfb72686fd4be34ddb09931b706af Mon Sep 17 00:00:00 2001 From: Valentine Wallace Date: Sun, 6 Nov 2022 16:06:44 -0500 Subject: [PATCH] Utils for forwarding intercepted htlcs + getting intercept scids See ChannelManager::forward_intercepted_htlc and ChannelManager::get_intercept_scid for details Co-authored-by: John Cantrell Co-authored-by: Valentine Wallace --- lightning/src/ln/channelmanager.rs | 68 +++++++++++++++ lightning/src/ln/payment_tests.rs | 128 ++++++++++++++++++++++++++++- lightning/src/util/events.rs | 11 ++- 3 files changed, 203 insertions(+), 4 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d80231897..de86a8e70 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -3051,6 +3051,57 @@ impl ChannelManager Result<(), APIError> { + let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier); + + let next_hop_scid = match self.channel_state.lock().unwrap().by_id.get(next_hop_channel_id) { + Some(chan) => chan.get_short_channel_id().unwrap_or(chan.outbound_scid_alias()), + None => return Err(APIError::APIMisuseError { + err: format!("Channel with id {:?} not found", next_hop_channel_id) + }) + }; + + let payment = self.pending_intercepted_htlcs.lock().unwrap().remove(&intercept_id) + .ok_or_else(|| APIError::APIMisuseError { + err: format!("Payment with intercept id {:?} not found", intercept_id.0) + })?; + + let routing = match payment.forward_info.routing { + PendingHTLCRouting::Forward { onion_packet, .. } => { + PendingHTLCRouting::Forward { onion_packet, short_channel_id: next_hop_scid } + }, + _ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted + }; + let pending_htlc_info = PendingHTLCInfo { + outgoing_amt_msat: amt_to_forward_msat, routing, ..payment.forward_info + }; + + let mut per_source_pending_forward = [( + payment.prev_short_channel_id, + payment.prev_funding_outpoint, + payment.prev_user_channel_id, + vec![(pending_htlc_info, payment.prev_htlc_id)] + )]; + self.forward_htlcs(&mut per_source_pending_forward); + Ok(()) + } + /// Processes HTLCs which are pending waiting on random forward delay. /// /// Should only really ever be called in response to a PendingHTLCsForwardable event. @@ -5772,6 +5823,23 @@ impl ChannelManager u64 { + let best_block_height = self.best_block.read().unwrap().height(); + let short_to_chan_info = self.short_to_chan_info.read().unwrap(); + loop { + let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block_height, &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager); + // Ensure the generated scid doesn't conflict with a real channel. + if short_to_chan_info.contains_key(&scid_candidate) { continue } + return scid_candidate + } + } + /// Gets inflight HTLC information by processing pending outbound payments that are in /// our channels. May be used during pathfinding to account for in-use channel liquidity. pub fn compute_inflight_htlcs(&self) -> InFlightHtlcs { diff --git a/lightning/src/ln/payment_tests.rs b/lightning/src/ln/payment_tests.rs index baaa60a7a..4b2ed1f0d 100644 --- a/lightning/src/ln/payment_tests.rs +++ b/lightning/src/ln/payment_tests.rs @@ -16,10 +16,11 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS use crate::chain::transaction::OutPoint; use crate::chain::keysinterface::KeysInterface; use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS; -use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS}; +use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, InterceptId, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS}; use crate::ln::msgs; use crate::ln::msgs::ChannelMessageHandler; -use crate::routing::router::{PaymentParameters, get_route}; +use crate::routing::gossip::RoutingFees; +use crate::routing::router::{find_route, get_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters}; use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider}; use crate::util::test_utils; use crate::util::errors::APIError; @@ -1371,3 +1372,126 @@ fn test_holding_cell_inflight_htlcs() { // Clear pending events so test doesn't throw a "Had excess message on node..." error nodes[0].node.get_and_clear_pending_msg_events(); } + +#[test] +fn forward_intercepted_payment() { + // Test that detecting an intercept scid on payment forward will signal LDK to generate an + // intercept event, which the LSP can then use to open a JIT channel to forward the payment. + let chanmon_cfgs = create_chanmon_cfgs(3); + let node_cfgs = create_node_cfgs(3, &chanmon_cfgs); + let mut chan_config = test_default_channel_config(); + chan_config.manually_accept_inbound_channels = true; + let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, Some(chan_config)]); + let nodes = create_network(3, &node_cfgs, &node_chanmgrs); + let scorer = test_utils::TestScorer::with_penalty(0); + let random_seed_bytes = chanmon_cfgs[0].keys_manager.get_secure_random_bytes(); + + let _ = create_announced_chan_between_nodes(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2; + + let amt_msat = 100_000; + let intercept_scid = nodes[1].node.get_intercept_scid(); + let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id()) + .with_route_hints(vec![ + RouteHint(vec![RouteHintHop { + src_node_id: nodes[1].node.get_our_node_id(), + short_channel_id: intercept_scid, + fees: RoutingFees { + base_msat: 1000, + proportional_millionths: 0, + }, + cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA, + htlc_minimum_msat: None, + htlc_maximum_msat: None, + }]) + ]) + .with_features(channelmanager::provided_invoice_features()); + let route_params = RouteParameters { + payment_params, + final_value_msat: amt_msat, + final_cltv_expiry_delta: TEST_FINAL_CLTV, + }; + let route = find_route( + &nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph, None, nodes[0].logger, + &scorer, &random_seed_bytes + ).unwrap(); + + let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60).unwrap(); + nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap(); + let payment_event = { + { + let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + added_monitors.clear(); + } + let mut events = nodes[0].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + SendEvent::from_event(events.remove(0)) + }; + nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]); + commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true); + + // Check that we generate the PaymentIntercepted event when an intercept forward is detected. + let events = nodes[1].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 1); + let (intercept_id, expected_outbound_amount_msat) = match events[0] { + crate::util::events::Event::HTLCIntercepted { + intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, inbound_amount_msat, requested_next_hop_scid: short_channel_id + } => { + assert_eq!(pmt_hash, payment_hash); + assert_eq!(inbound_amount_msat, route.get_total_amount() + route.get_total_fees()); + assert_eq!(short_channel_id, intercept_scid); + (intercept_id, expected_outbound_amount_msat) + }, + _ => panic!() + }; + + // Check for unknown channel id error. + let unknown_chan_id_err = nodes[1].node.forward_intercepted_htlc(intercept_id, &[42; 32], nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err(); + assert_eq!(unknown_chan_id_err , APIError::APIMisuseError { err: format!("Channel with id {:?} not found", [42; 32]) }); + + // Open the just-in-time channel so the payment can then be forwarded. + let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None); + + // Check for unknown intercept id error. + let unknown_intercept_id = InterceptId([42; 32]); + let unknown_intercept_id_err = nodes[1].node.forward_intercepted_htlc(unknown_intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap_err(); + assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {:?} not found", unknown_intercept_id.0) }); + + // Finally, forward the intercepted payment through and claim it. + nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap(); + expect_pending_htlcs_forwardable!(nodes[1]); + + let payment_event = { + { + let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap(); + assert_eq!(added_monitors.len(), 1); + added_monitors.clear(); + } + let mut events = nodes[1].node.get_and_clear_pending_msg_events(); + assert_eq!(events.len(), 1); + SendEvent::from_event(events.remove(0)) + }; + nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]); + commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true); + expect_pending_htlcs_forwardable!(nodes[2]); + + let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap(); + expect_payment_received!(&nodes[2], payment_hash, payment_secret, amt_msat, Some(payment_preimage), nodes[2].node.get_our_node_id()); + do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage); + let events = nodes[0].node.get_and_clear_pending_events(); + assert_eq!(events.len(), 2); + match events[0] { + Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => { + assert_eq!(payment_preimage, *ev_preimage); + assert_eq!(payment_hash, *ev_hash); + assert_eq!(fee_paid_msat, &Some(1000)); + }, + _ => panic!("Unexpected event") + } + match events[1] { + Event::PaymentPathSuccessful { payment_hash: hash, .. } => { + assert_eq!(hash, Some(payment_hash)); + }, + _ => panic!("Unexpected event") + } +} diff --git a/lightning/src/util/events.rs b/lightning/src/util/events.rs index 268f9c908..d7e7bb1e3 100644 --- a/lightning/src/util/events.rs +++ b/lightning/src/util/events.rs @@ -610,11 +610,18 @@ pub enum Event { /// now + 5*time_forwardable). time_forwardable: Duration, }, - /// Used to indicate that we've intercepted an HTLC forward. + /// Used to indicate that we've intercepted an HTLC forward. This event will only be generated if + /// you've encoded an intercept scid in the receiver's invoice route hints using + /// [`ChannelManager::get_intercept_scid`]. + /// + /// [`ChannelManager::get_intercept_scid`]: crate::ln::channelmanager::ChannelManager::get_intercept_scid HTLCIntercepted { /// An id to help LDK identify which HTLC is being forwarded or failed. intercept_id: InterceptId, - /// The fake scid that was programmed as the next hop's scid. + /// The fake scid that was programmed as the next hop's scid, generated using + /// [`ChannelManager::get_intercept_scid`]. + /// + /// [`ChannelManager::get_intercept_scid`]: crate::ln::channelmanager::ChannelManager::get_intercept_scid requested_next_hop_scid: u64, /// The payment hash used for this HTLC. payment_hash: PaymentHash, -- 2.39.5