Skip to content

Commit 4cc7ff7

Browse files
Support multi-hop blinded paths for RAA
XXX
1 parent 9318a69 commit 4cc7ff7

File tree

3 files changed

+145
-1
lines changed

3 files changed

+145
-1
lines changed

lightning/src/ln/async_payments_tests.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2587,6 +2587,131 @@ fn held_htlc_timeout() {
25872587
);
25882588
}
25892589

2590+
#[test]
2591+
fn fallback_to_one_hop_release_htlc_path() {
2592+
// Check that if the sender's LSP's message router fails to find a blinded path when creating a
2593+
// path for the release_held_htlc message, they will fall back to manually creating a 1-hop
2594+
// blinded path.
2595+
let chanmon_cfgs = create_chanmon_cfgs(4);
2596+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
2597+
2598+
let (sender_cfg, recipient_cfg) = (often_offline_node_cfg(), often_offline_node_cfg());
2599+
let mut sender_lsp_cfg = test_default_channel_config();
2600+
sender_lsp_cfg.enable_htlc_hold = true;
2601+
let mut invoice_server_cfg = test_default_channel_config();
2602+
invoice_server_cfg.accept_forwards_to_priv_channels = true;
2603+
2604+
let node_chanmgrs = create_node_chanmgrs(
2605+
4,
2606+
&node_cfgs,
2607+
&[Some(sender_cfg), Some(sender_lsp_cfg), Some(invoice_server_cfg), Some(recipient_cfg)],
2608+
);
2609+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
2610+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2611+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2612+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
2613+
// Make sure all nodes are at the same block height
2614+
let node_max_height =
2615+
nodes.iter().map(|node| node.blocks.lock().unwrap().len()).max().unwrap() as u32;
2616+
connect_blocks(&nodes[0], node_max_height - nodes[0].best_block_info().1);
2617+
connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1);
2618+
connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1);
2619+
connect_blocks(&nodes[3], node_max_height - nodes[3].best_block_info().1);
2620+
let sender = &nodes[0];
2621+
let sender_lsp = &nodes[1];
2622+
let invoice_server = &nodes[2];
2623+
let recipient = &nodes[3];
2624+
2625+
let recipient_id = vec![42; 32];
2626+
let inv_server_paths =
2627+
invoice_server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
2628+
recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
2629+
expect_offer_paths_requests(recipient, &[sender, sender_lsp, invoice_server]);
2630+
let invoice =
2631+
pass_static_invoice_server_messages(invoice_server, recipient, recipient_id.clone())
2632+
.invoice;
2633+
2634+
let offer = recipient.node.get_async_receive_offer().unwrap();
2635+
let amt_msat = 5000;
2636+
let payment_id = PaymentId([1; 32]);
2637+
let params = RouteParametersConfig::default();
2638+
sender
2639+
.node
2640+
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(1), params)
2641+
.unwrap();
2642+
2643+
// Forward invreq to server, pass static invoice back, check that htlc was locked in/monitor was
2644+
// added
2645+
let (peer_id, invreq_om) = extract_invoice_request_om(sender, &[sender_lsp, invoice_server]);
2646+
invoice_server.onion_messenger.handle_onion_message(peer_id, &invreq_om);
2647+
2648+
let mut events = invoice_server.node.get_and_clear_pending_events();
2649+
assert_eq!(events.len(), 1);
2650+
let reply_path = match events.pop().unwrap() {
2651+
Event::StaticInvoiceRequested { recipient_id: ev_id, invoice_slot: _, reply_path } => {
2652+
assert_eq!(recipient_id, ev_id);
2653+
reply_path
2654+
},
2655+
_ => panic!(),
2656+
};
2657+
2658+
invoice_server.node.send_static_invoice(invoice.clone(), reply_path).unwrap();
2659+
let (peer_node_id, static_invoice_om, _) =
2660+
extract_static_invoice_om(invoice_server, &[sender_lsp, sender, recipient]);
2661+
2662+
// The sender should lock in the held HTLC with their LSP right after receiving the static invoice.
2663+
sender.onion_messenger.handle_onion_message(peer_node_id, &static_invoice_om);
2664+
check_added_monitors(sender, 1);
2665+
let commitment_update = get_htlc_update_msgs!(sender, sender_lsp.node.get_our_node_id());
2666+
let update_add = commitment_update.update_add_htlcs[0].clone();
2667+
let payment_hash = update_add.payment_hash;
2668+
assert!(update_add.hold_htlc.is_some());
2669+
2670+
// Force the sender_lsp's call to MessageRouter::create_blinded_paths to fail so it has to fall
2671+
// back to a 1-hop blinded path when creating the paths for its revoke_and_ack message.
2672+
sender_lsp.message_router.create_blinded_paths_res_override.lock().unwrap().1 = Some(Err(()));
2673+
2674+
sender_lsp.node.handle_update_add_htlc(sender.node.get_our_node_id(), &update_add);
2675+
commitment_signed_dance!(sender_lsp, sender, &commitment_update.commitment_signed, false, true);
2676+
2677+
// Check that we actually had to fall back to a 1-hop path.
2678+
assert!(sender_lsp.message_router.create_blinded_paths_res_override.lock().unwrap().0 > 0);
2679+
sender_lsp.message_router.create_blinded_paths_res_override.lock().unwrap().1 = None;
2680+
2681+
sender_lsp.node.process_pending_htlc_forwards();
2682+
let (peer_id, held_htlc_om) =
2683+
extract_held_htlc_available_oms(sender, &[sender_lsp, invoice_server, recipient])
2684+
.pop()
2685+
.unwrap();
2686+
recipient.onion_messenger.handle_onion_message(peer_id, &held_htlc_om);
2687+
2688+
// The release_htlc OM should go straight to the sender's LSP since they created a 1-hop blinded
2689+
// path to themselves for receiving it.
2690+
let release_htlc_om = recipient
2691+
.onion_messenger
2692+
.next_onion_message_for_peer(sender_lsp.node.get_our_node_id())
2693+
.unwrap();
2694+
sender_lsp
2695+
.onion_messenger
2696+
.handle_onion_message(recipient.node.get_our_node_id(), &release_htlc_om);
2697+
2698+
sender_lsp.node.process_pending_htlc_forwards();
2699+
let mut events = sender_lsp.node.get_and_clear_pending_msg_events();
2700+
assert_eq!(events.len(), 1);
2701+
let ev = remove_first_msg_event_to_node(&invoice_server.node.get_our_node_id(), &mut events);
2702+
check_added_monitors!(sender_lsp, 1);
2703+
2704+
let path: &[&Node] = &[invoice_server, recipient];
2705+
let args = PassAlongPathArgs::new(sender_lsp, path, amt_msat, payment_hash, ev);
2706+
let claimable_ev = do_pass_along_path(args).unwrap();
2707+
2708+
let route: &[&[&Node]] = &[&[sender_lsp, invoice_server, recipient]];
2709+
let keysend_preimage = extract_payment_preimage(&claimable_ev);
2710+
let (res, _) =
2711+
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
2712+
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(invoice)));
2713+
}
2714+
25902715
#[test]
25912716
fn intercepted_hold_htlc() {
25922717
// Test a payment `sender --> LSP --> recipient` such that the HTLC is both a hold htlc and an

lightning/src/offers/flow.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1177,9 +1177,14 @@ where
11771177
where
11781178
ES::Target: EntropySource,
11791179
{
1180-
// In the future, we should support multi-hop paths here.
11811180
let context =
11821181
MessageContext::AsyncPayments(AsyncPaymentsContext::ReleaseHeldHtlc { intercept_id });
1182+
if let Ok(mut paths) = self.create_blinded_paths(context.clone()) {
1183+
if let Some(path) = paths.pop() {
1184+
return path;
1185+
}
1186+
}
1187+
11831188
let num_dummy_hops = PADDED_PATH_LENGTH.saturating_sub(1);
11841189
BlindedMessagePath::new_with_dummy_hops(
11851190
&[],

lightning/src/util/test_utils.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,9 @@ pub enum TestMessageRouterInternal<'a> {
331331
pub struct TestMessageRouter<'a> {
332332
pub inner: TestMessageRouterInternal<'a>,
333333
pub peers_override: Mutex<Vec<PublicKey>>,
334+
// An override result for Self::create_blinded_paths, plus a tracker for how many times the
335+
// overridden result has been returned.
336+
pub create_blinded_paths_res_override: Mutex<(u8, Option<Result<Vec<BlindedMessagePath>, ()>>)>,
334337
}
335338

336339
impl<'a> TestMessageRouter<'a> {
@@ -343,6 +346,7 @@ impl<'a> TestMessageRouter<'a> {
343346
entropy_source,
344347
)),
345348
peers_override: Mutex::new(Vec::new()),
349+
create_blinded_paths_res_override: Mutex::new((0, None)),
346350
}
347351
}
348352

@@ -355,6 +359,7 @@ impl<'a> TestMessageRouter<'a> {
355359
entropy_source,
356360
)),
357361
peers_override: Mutex::new(Vec::new()),
362+
create_blinded_paths_res_override: Mutex::new((0, None)),
358363
}
359364
}
360365
}
@@ -382,6 +387,15 @@ impl<'a> MessageRouter for TestMessageRouter<'a> {
382387
&self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey,
383388
context: MessageContext, peers: Vec<MessageForwardNode>, secp_ctx: &Secp256k1<T>,
384389
) -> Result<Vec<BlindedMessagePath>, ()> {
390+
{
391+
let mut res_override = self.create_blinded_paths_res_override.lock().unwrap();
392+
if let Some(res) = &res_override.1 {
393+
let res = res.clone();
394+
res_override.0 += 1;
395+
return res;
396+
}
397+
}
398+
385399
let mut peers = peers;
386400
{
387401
let peers_override = self.peers_override.lock().unwrap();

0 commit comments

Comments
 (0)