Skip to content

Commit c1f1b78

Browse files
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 <johncantrell97@gmail.com> Co-authored-by: Valentine Wallace <vwallace@protonmail.com>
1 parent 8fe7cbe commit c1f1b78

File tree

3 files changed

+203
-4
lines changed

3 files changed

+203
-4
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3051,6 +3051,57 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
30513051
Ok(())
30523052
}
30533053

3054+
/// Attempts to forward an intercepted HTLC over the provided channel id and with the provided
3055+
/// amount to forward. Should only be called in response to an [`HTLCIntercepted`] event.
3056+
///
3057+
/// Intercepted HTLCs can be useful for Lightning Service Providers (LSPs) to open a just-in-time
3058+
/// channel to a receiving node if the node lacks sufficient inbound liquidity.
3059+
///
3060+
/// To make use of intercepted HTLCs, use [`ChannelManager::get_intercept_scid`] to generate short
3061+
/// channel id(s) to put in the receiver's invoice route hints. These route hints will signal to
3062+
/// LDK to generate an [`HTLCIntercepted`] event when it receives the forwarded HTLC.
3063+
///
3064+
/// Note that LDK does not enforce fee requirements in `amt_to_forward_msat`, and will not stop
3065+
/// you from forwarding more than you received.
3066+
///
3067+
/// [`HTLCIntercepted`]: events::Event::HTLCIntercepted
3068+
// TODO: when we move to deciding the best outbound channel at forward time, only take
3069+
// `next_node_id` and not `next_hop_channel_id`
3070+
pub fn forward_intercepted_htlc(&self, intercept_id: InterceptId, next_hop_channel_id: &[u8; 32], _next_node_id: PublicKey, amt_to_forward_msat: u64) -> Result<(), APIError> {
3071+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
3072+
3073+
let next_hop_scid = match self.channel_state.lock().unwrap().by_id.get(next_hop_channel_id) {
3074+
Some(chan) => chan.get_short_channel_id().unwrap_or(chan.outbound_scid_alias()),
3075+
None => return Err(APIError::APIMisuseError {
3076+
err: format!("Channel with id {:?} not found", next_hop_channel_id)
3077+
})
3078+
};
3079+
3080+
let payment = self.pending_intercepted_htlcs.lock().unwrap().remove(&intercept_id)
3081+
.ok_or_else(|| APIError::APIMisuseError {
3082+
err: format!("Payment with intercept id {:?} not found", intercept_id.0)
3083+
})?;
3084+
3085+
let routing = match payment.forward_info.routing {
3086+
PendingHTLCRouting::Forward { onion_packet, .. } => {
3087+
PendingHTLCRouting::Forward { onion_packet, short_channel_id: next_hop_scid }
3088+
},
3089+
_ => unreachable!() // Only `PendingHTLCRouting::Forward`s are intercepted
3090+
};
3091+
let pending_htlc_info = PendingHTLCInfo {
3092+
outgoing_amt_msat: amt_to_forward_msat, routing, ..payment.forward_info
3093+
};
3094+
3095+
let mut per_source_pending_forward = [(
3096+
payment.prev_short_channel_id,
3097+
payment.prev_funding_outpoint,
3098+
payment.prev_user_channel_id,
3099+
vec![(pending_htlc_info, payment.prev_htlc_id)]
3100+
)];
3101+
self.forward_htlcs(&mut per_source_pending_forward);
3102+
Ok(())
3103+
}
3104+
30543105
/// Processes HTLCs which are pending waiting on random forward delay.
30553106
///
30563107
/// Should only really ever be called in response to a PendingHTLCsForwardable event.
@@ -5772,6 +5823,23 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
57725823
}
57735824
}
57745825

5826+
/// Gets a fake short channel id for use in receiving intercepted payments. These fake scids are
5827+
/// used when constructing the route hints for HTLCs intended to be intercepted. See
5828+
/// [`ChannelManager::forward_intercepted_htlc`].
5829+
///
5830+
/// Note that this method is not guaranteed to return unique values, you may need to call it a few
5831+
/// times to get a unique scid.
5832+
pub fn get_intercept_scid(&self) -> u64 {
5833+
let best_block_height = self.best_block.read().unwrap().height();
5834+
let short_to_chan_info = self.short_to_chan_info.read().unwrap();
5835+
loop {
5836+
let scid_candidate = fake_scid::Namespace::Intercept.get_fake_scid(best_block_height, &self.genesis_hash, &self.fake_scid_rand_bytes, &self.keys_manager);
5837+
// Ensure the generated scid doesn't conflict with a real channel.
5838+
if short_to_chan_info.contains_key(&scid_candidate) { continue }
5839+
return scid_candidate
5840+
}
5841+
}
5842+
57755843
/// Gets inflight HTLC information by processing pending outbound payments that are in
57765844
/// our channels. May be used during pathfinding to account for in-use channel liquidity.
57775845
pub fn compute_inflight_htlcs(&self) -> InFlightHtlcs {

lightning/src/ln/payment_tests.rs

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ use crate::chain::channelmonitor::{ANTI_REORG_DELAY, LATENCY_GRACE_PERIOD_BLOCKS
1616
use crate::chain::transaction::OutPoint;
1717
use crate::chain::keysinterface::KeysInterface;
1818
use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
19-
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
19+
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, InterceptId, MPP_TIMEOUT_TICKS, MIN_CLTV_EXPIRY_DELTA, PaymentId, PaymentSendFailure, IDEMPOTENCY_TIMEOUT_TICKS};
2020
use crate::ln::msgs;
2121
use crate::ln::msgs::ChannelMessageHandler;
22-
use crate::routing::router::{PaymentParameters, get_route};
22+
use crate::routing::gossip::RoutingFees;
23+
use crate::routing::router::{find_route, get_route, PaymentParameters, RouteHint, RouteHintHop, RouteParameters};
2324
use crate::util::events::{ClosureReason, Event, HTLCDestination, MessageSendEvent, MessageSendEventsProvider};
2425
use crate::util::test_utils;
2526
use crate::util::errors::APIError;
@@ -1371,3 +1372,126 @@ fn test_holding_cell_inflight_htlcs() {
13711372
// Clear pending events so test doesn't throw a "Had excess message on node..." error
13721373
nodes[0].node.get_and_clear_pending_msg_events();
13731374
}
1375+
1376+
#[test]
1377+
fn forward_intercepted_payment() {
1378+
// Test that detecting an intercept scid on payment forward will signal LDK to generate an
1379+
// intercept event, which the LSP can then use to open a JIT channel to forward the payment.
1380+
let chanmon_cfgs = create_chanmon_cfgs(3);
1381+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1382+
let mut chan_config = test_default_channel_config();
1383+
chan_config.manually_accept_inbound_channels = true;
1384+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, Some(chan_config)]);
1385+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1386+
let scorer = test_utils::TestScorer::with_penalty(0);
1387+
let random_seed_bytes = chanmon_cfgs[0].keys_manager.get_secure_random_bytes();
1388+
1389+
let _ = create_announced_chan_between_nodes(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
1390+
1391+
let amt_msat = 100_000;
1392+
let intercept_scid = nodes[1].node.get_intercept_scid();
1393+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())
1394+
.with_route_hints(vec![
1395+
RouteHint(vec![RouteHintHop {
1396+
src_node_id: nodes[1].node.get_our_node_id(),
1397+
short_channel_id: intercept_scid,
1398+
fees: RoutingFees {
1399+
base_msat: 1000,
1400+
proportional_millionths: 0,
1401+
},
1402+
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA,
1403+
htlc_minimum_msat: None,
1404+
htlc_maximum_msat: None,
1405+
}])
1406+
])
1407+
.with_features(channelmanager::provided_invoice_features());
1408+
let route_params = RouteParameters {
1409+
payment_params,
1410+
final_value_msat: amt_msat,
1411+
final_cltv_expiry_delta: TEST_FINAL_CLTV,
1412+
};
1413+
let route = find_route(
1414+
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph, None, nodes[0].logger,
1415+
&scorer, &random_seed_bytes
1416+
).unwrap();
1417+
1418+
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60).unwrap();
1419+
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
1420+
let payment_event = {
1421+
{
1422+
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
1423+
assert_eq!(added_monitors.len(), 1);
1424+
added_monitors.clear();
1425+
}
1426+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
1427+
assert_eq!(events.len(), 1);
1428+
SendEvent::from_event(events.remove(0))
1429+
};
1430+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
1431+
commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true);
1432+
1433+
// Check that we generate the PaymentIntercepted event when an intercept forward is detected.
1434+
let events = nodes[1].node.get_and_clear_pending_events();
1435+
assert_eq!(events.len(), 1);
1436+
let (intercept_id, expected_outbound_amount_msat) = match events[0] {
1437+
crate::util::events::Event::HTLCIntercepted {
1438+
intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, inbound_amount_msat, requested_next_hop_scid: short_channel_id
1439+
} => {
1440+
assert_eq!(pmt_hash, payment_hash);
1441+
assert_eq!(inbound_amount_msat, route.get_total_amount() + route.get_total_fees());
1442+
assert_eq!(short_channel_id, intercept_scid);
1443+
(intercept_id, expected_outbound_amount_msat)
1444+
},
1445+
_ => panic!()
1446+
};
1447+
1448+
// Check for unknown channel id error.
1449+
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();
1450+
assert_eq!(unknown_chan_id_err , APIError::APIMisuseError { err: format!("Channel with id {:?} not found", [42; 32]) });
1451+
1452+
// Open the just-in-time channel so the payment can then be forwarded.
1453+
let (_, channel_id) = open_zero_conf_channel(&nodes[1], &nodes[2], None);
1454+
1455+
// Check for unknown intercept id error.
1456+
let unknown_intercept_id = InterceptId([42; 32]);
1457+
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();
1458+
assert_eq!(unknown_intercept_id_err , APIError::APIMisuseError { err: format!("Payment with intercept id {:?} not found", unknown_intercept_id.0) });
1459+
1460+
// Finally, forward the intercepted payment through and claim it.
1461+
nodes[1].node.forward_intercepted_htlc(intercept_id, &channel_id, nodes[2].node.get_our_node_id(), expected_outbound_amount_msat).unwrap();
1462+
expect_pending_htlcs_forwardable!(nodes[1]);
1463+
1464+
let payment_event = {
1465+
{
1466+
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
1467+
assert_eq!(added_monitors.len(), 1);
1468+
added_monitors.clear();
1469+
}
1470+
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
1471+
assert_eq!(events.len(), 1);
1472+
SendEvent::from_event(events.remove(0))
1473+
};
1474+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
1475+
commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true);
1476+
expect_pending_htlcs_forwardable!(nodes[2]);
1477+
1478+
let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
1479+
expect_payment_received!(&nodes[2], payment_hash, payment_secret, amt_msat, Some(payment_preimage), nodes[2].node.get_our_node_id());
1480+
do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage);
1481+
let events = nodes[0].node.get_and_clear_pending_events();
1482+
assert_eq!(events.len(), 2);
1483+
match events[0] {
1484+
Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
1485+
assert_eq!(payment_preimage, *ev_preimage);
1486+
assert_eq!(payment_hash, *ev_hash);
1487+
assert_eq!(fee_paid_msat, &Some(1000));
1488+
},
1489+
_ => panic!("Unexpected event")
1490+
}
1491+
match events[1] {
1492+
Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
1493+
assert_eq!(hash, Some(payment_hash));
1494+
},
1495+
_ => panic!("Unexpected event")
1496+
}
1497+
}

lightning/src/util/events.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -610,11 +610,18 @@ pub enum Event {
610610
/// now + 5*time_forwardable).
611611
time_forwardable: Duration,
612612
},
613-
/// Used to indicate that we've intercepted an HTLC forward.
613+
/// Used to indicate that we've intercepted an HTLC forward. This event will only be generated if
614+
/// you've encoded an intercept scid in the receiver's invoice route hints using
615+
/// [`ChannelManager::get_intercept_scid`].
616+
///
617+
/// [`ChannelManager::get_intercept_scid`]: crate::ln::channelmanager::ChannelManager::get_intercept_scid
614618
HTLCIntercepted {
615619
/// An id to help LDK identify which HTLC is being forwarded or failed.
616620
intercept_id: InterceptId,
617-
/// The fake scid that was programmed as the next hop's scid.
621+
/// The fake scid that was programmed as the next hop's scid, generated using
622+
/// [`ChannelManager::get_intercept_scid`].
623+
///
624+
/// [`ChannelManager::get_intercept_scid`]: crate::ln::channelmanager::ChannelManager::get_intercept_scid
618625
requested_next_hop_scid: u64,
619626
/// The payment hash used for this HTLC.
620627
payment_hash: PaymentHash,

0 commit comments

Comments
 (0)