Skip to content

Commit fcc39fa

Browse files
Utils for forwarding intercepted payments + getting intercept scids
See ChannelManager::forward_intercepted_payment 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 e2b406e commit fcc39fa

File tree

2 files changed

+162
-1
lines changed

2 files changed

+162
-1
lines changed

lightning/src/ln/channelmanager.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3157,6 +3157,40 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
31573157
Ok(())
31583158
}
31593159

3160+
/// Attempts to forward an intercepted payment over the provided scid and with the provided
3161+
/// amt_to_forward. Should only be called in response to a PaymentIntercepted event.
3162+
// TODO expand docs
3163+
pub fn forward_intercepted_payment(&self, intercept_id: InterceptId, short_channel_id: u64, amt_to_forward: u64) -> Result<(), APIError> {
3164+
let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(&self.total_consistency_lock, &self.persistence_notifier);
3165+
3166+
let payment = self.pending_intercepted_payments.lock().unwrap().remove(&intercept_id)
3167+
.ok_or_else(|| APIError::APIMisuseError {
3168+
err: format!("Payment with InterceptId {:?} not found", intercept_id)
3169+
})?;
3170+
3171+
if self.short_to_chan_info.read().unwrap().get(&short_channel_id).is_none() {
3172+
return Err(APIError::APIMisuseError {
3173+
err: format!("Channel with short channel id {:?} not found", short_channel_id)
3174+
})
3175+
}
3176+
3177+
let routing = match payment.forward_info.routing {
3178+
PendingHTLCRouting::Forward { onion_packet, .. } => {
3179+
PendingHTLCRouting::Forward { onion_packet, short_channel_id }
3180+
},
3181+
_ => unreachable!()
3182+
};
3183+
let pending_htlc_info = PendingHTLCInfo { amt_to_forward, routing, ..payment.forward_info };
3184+
3185+
let mut per_source_pending_forward = [(
3186+
payment.prev_short_channel_id,
3187+
payment.prev_funding_outpoint,
3188+
vec![(pending_htlc_info, payment.prev_htlc_id)]
3189+
)];
3190+
self.forward_htlcs(&mut per_source_pending_forward);
3191+
Ok(())
3192+
}
3193+
31603194
/// Processes HTLCs which are pending waiting on random forward delay.
31613195
///
31623196
/// Should only really ever be called in response to a PendingHTLCsForwardable event.
@@ -5792,6 +5826,20 @@ impl<M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelManager<M, T, K, F
57925826
}
57935827
}
57945828

5829+
/// Gets a fake short channel id for use in receiving intercepted payments. These fake scids
5830+
/// are used when constructing the route hints for payments intended to be intercepted.
5831+
// TODO doc links
5832+
pub fn get_intercept_scid(&self) -> u64 {
5833+
let short_to_chan_info = self.short_to_chan_info.read().unwrap();
5834+
let best_block = self.best_block.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+
57955843
#[cfg(any(test, fuzzing, feature = "_test_utils"))]
57965844
pub fn get_and_clear_pending_events(&self) -> Vec<events::Event> {
57975845
let events = core::cell::RefCell::new(Vec::new());

lightning/src/ln/payment_tests.rs

Lines changed: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ use crate::ln::channel::EXPIRE_PREV_CONFIG_TICKS;
1919
use crate::ln::channelmanager::{self, BREAKDOWN_TIMEOUT, ChannelManager, ChannelManagerReadArgs, 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;
@@ -1385,3 +1386,115 @@ fn abandoned_send_payment_idempotent() {
13851386
pass_along_route(&nodes[0], &[&[&nodes[1]]], 100_000, second_payment_hash, second_payment_secret);
13861387
claim_payment(&nodes[0], &[&nodes[1]], second_payment_preimage);
13871388
}
1389+
1390+
#[test]
1391+
fn forward_intercepted_payment() {
1392+
// Test that detecting an intercept scid on payment forward will signal LDK to generate an
1393+
// intercept event, which the LSP can then use to open a JIT channel to forward the payment.
1394+
let chanmon_cfgs = create_chanmon_cfgs(3);
1395+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1396+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
1397+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1398+
let scorer = test_utils::TestScorer::with_penalty(0);
1399+
let random_seed_bytes = chanmon_cfgs[0].keys_manager.get_secure_random_bytes();
1400+
1401+
let _ = create_announced_chan_between_nodes(&nodes, 0, 1, channelmanager::provided_init_features(), channelmanager::provided_init_features()).2;
1402+
1403+
let amt_msat = 100_000;
1404+
let intercept_scid = nodes[1].node.get_intercept_scid();
1405+
let payment_params = PaymentParameters::from_node_id(nodes[2].node.get_our_node_id())
1406+
.with_route_hints(vec![
1407+
RouteHint(vec![RouteHintHop {
1408+
src_node_id: nodes[1].node.get_our_node_id(),
1409+
short_channel_id: intercept_scid,
1410+
fees: RoutingFees {
1411+
base_msat: 1000,
1412+
proportional_millionths: 0,
1413+
},
1414+
cltv_expiry_delta: 130,
1415+
htlc_minimum_msat: None,
1416+
htlc_maximum_msat: None,
1417+
}])
1418+
])
1419+
.with_features(channelmanager::provided_invoice_features());
1420+
let route_params = RouteParameters {
1421+
payment_params,
1422+
final_value_msat: amt_msat,
1423+
final_cltv_expiry_delta: TEST_FINAL_CLTV,
1424+
};
1425+
let route = find_route(
1426+
&nodes[0].node.get_our_node_id(), &route_params, &nodes[0].network_graph, None, nodes[0].logger,
1427+
&scorer, &random_seed_bytes
1428+
).unwrap();
1429+
1430+
let (payment_hash, payment_secret) = nodes[2].node.create_inbound_payment(Some(amt_msat), 60 * 60).unwrap();
1431+
nodes[0].node.send_payment(&route, payment_hash, &Some(payment_secret), PaymentId(payment_hash.0)).unwrap();
1432+
let payment_event = {
1433+
{
1434+
let mut added_monitors = nodes[0].chain_monitor.added_monitors.lock().unwrap();
1435+
assert_eq!(added_monitors.len(), 1);
1436+
added_monitors.clear();
1437+
}
1438+
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
1439+
assert_eq!(events.len(), 1);
1440+
SendEvent::from_event(events.remove(0))
1441+
};
1442+
nodes[1].node.handle_update_add_htlc(&nodes[0].node.get_our_node_id(), &payment_event.msgs[0]);
1443+
commitment_signed_dance!(nodes[1], nodes[0], &payment_event.commitment_msg, false, true);
1444+
1445+
// Check that we generate the PaymentIntercepted event when an intercept forward is detected.
1446+
let events = nodes[1].node.get_and_clear_pending_events();
1447+
assert_eq!(events.len(), 1);
1448+
let (intercept_id, expected_outbound_amount_msat) = match events[0] {
1449+
crate::util::events::Event::PaymentIntercepted {
1450+
intercept_id, expected_outbound_amount_msat, payment_hash: pmt_hash, inbound_amount_msat, short_channel_id
1451+
} => {
1452+
assert_eq!(pmt_hash, payment_hash);
1453+
assert_eq!(inbound_amount_msat, route.get_total_amount() + route.get_total_fees());
1454+
assert_eq!(short_channel_id, intercept_scid);
1455+
(intercept_id, expected_outbound_amount_msat)
1456+
},
1457+
_ => panic!()
1458+
};
1459+
1460+
// Open the just-in-time channel so the payment can then be forwarded.
1461+
let scid = create_announced_chan_between_nodes(&nodes, 1, 2, channelmanager::provided_init_features(), channelmanager::provided_init_features()).0.contents.short_channel_id;
1462+
1463+
// Finally, forward the intercepted payment through and claim it.
1464+
nodes[1].node.forward_intercepted_payment(intercept_id, scid, expected_outbound_amount_msat).unwrap();
1465+
expect_pending_htlcs_forwardable!(nodes[1]);
1466+
1467+
let payment_event = {
1468+
{
1469+
let mut added_monitors = nodes[1].chain_monitor.added_monitors.lock().unwrap();
1470+
assert_eq!(added_monitors.len(), 1);
1471+
added_monitors.clear();
1472+
}
1473+
let mut events = nodes[1].node.get_and_clear_pending_msg_events();
1474+
assert_eq!(events.len(), 1);
1475+
SendEvent::from_event(events.remove(0))
1476+
};
1477+
nodes[2].node.handle_update_add_htlc(&nodes[1].node.get_our_node_id(), &payment_event.msgs[0]);
1478+
commitment_signed_dance!(nodes[2], nodes[1], &payment_event.commitment_msg, false, true);
1479+
expect_pending_htlcs_forwardable!(nodes[2]);
1480+
1481+
let payment_preimage = nodes[2].node.get_payment_preimage(payment_hash, payment_secret).unwrap();
1482+
expect_payment_received!(&nodes[2], payment_hash, payment_secret, amt_msat, Some(payment_preimage));
1483+
do_claim_payment_along_route(&nodes[0], &vec!(&vec!(&nodes[1], &nodes[2])[..]), false, payment_preimage);
1484+
let events = nodes[0].node.get_and_clear_pending_events();
1485+
assert_eq!(events.len(), 2);
1486+
match events[0] {
1487+
Event::PaymentSent { payment_preimage: ref ev_preimage, payment_hash: ref ev_hash, ref fee_paid_msat, .. } => {
1488+
assert_eq!(payment_preimage, *ev_preimage);
1489+
assert_eq!(payment_hash, *ev_hash);
1490+
assert_eq!(fee_paid_msat, &Some(1000));
1491+
},
1492+
_ => panic!("Unexpected event")
1493+
}
1494+
match events[1] {
1495+
Event::PaymentPathSuccessful { payment_hash: hash, .. } => {
1496+
assert_eq!(hash, Some(payment_hash));
1497+
},
1498+
_ => panic!("Unexpected event")
1499+
}
1500+
}

0 commit comments

Comments
 (0)