Skip to content

Commit 27f4930

Browse files
Support multi-hop blinded paths for RAA
We've been including blinded paths in our RAA messages for async payments purposes, but had to use 1-hop blinded paths because we can't acquire additional peer locks while holding a specific channel's peer lock during the RAA creation process. In the past few commits we started caching the set of peers in the OffersMessageFlow, so we can now generate multi-hop blinded paths during the RAA creation process without needing to acquire additional peer locks.
1 parent 9698772 commit 27f4930

File tree

3 files changed

+104
-1
lines changed

3 files changed

+104
-1
lines changed

lightning/src/ln/async_payments_tests.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3285,6 +3285,90 @@ fn async_payment_mpp() {
32853285
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, expected_route, keysend_preimage));
32863286
}
32873287

3288+
#[test]
3289+
fn fallback_to_one_hop_release_htlc_path() {
3290+
// Check that if the sender's LSP's message router fails to find a blinded path when creating a
3291+
// path for the release_held_htlc message, they will fall back to manually creating a 1-hop
3292+
// blinded path.
3293+
let chanmon_cfgs = create_chanmon_cfgs(4);
3294+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
3295+
3296+
let (sender_cfg, recipient_cfg) = (often_offline_node_cfg(), often_offline_node_cfg());
3297+
let mut sender_lsp_cfg = test_default_channel_config();
3298+
sender_lsp_cfg.enable_htlc_hold = true;
3299+
let mut invoice_server_cfg = test_default_channel_config();
3300+
invoice_server_cfg.accept_forwards_to_priv_channels = true;
3301+
3302+
let node_chanmgrs = create_node_chanmgrs(
3303+
4,
3304+
&node_cfgs,
3305+
&[Some(sender_cfg), Some(sender_lsp_cfg), Some(invoice_server_cfg), Some(recipient_cfg)],
3306+
);
3307+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
3308+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
3309+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
3310+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
3311+
// Make sure all nodes are at the same block height
3312+
let node_max_height =
3313+
nodes.iter().map(|node| node.blocks.lock().unwrap().len()).max().unwrap() as u32;
3314+
connect_blocks(&nodes[0], node_max_height - nodes[0].best_block_info().1);
3315+
connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1);
3316+
connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1);
3317+
connect_blocks(&nodes[3], node_max_height - nodes[3].best_block_info().1);
3318+
let sender = &nodes[0];
3319+
let sender_lsp = &nodes[1];
3320+
let invoice_server = &nodes[2];
3321+
let recipient = &nodes[3];
3322+
3323+
let amt_msat = 5000;
3324+
let (static_invoice, peer_node_id, static_invoice_om) =
3325+
build_async_offer_and_init_payment(amt_msat, &nodes);
3326+
3327+
// Force the sender_lsp's call to MessageRouter::create_blinded_paths to fail so it has to fall
3328+
// back to a 1-hop blinded path when creating the paths for its revoke_and_ack message.
3329+
sender_lsp.message_router.create_blinded_paths_res_override.lock().unwrap().1 = Some(Err(()));
3330+
3331+
let payment_hash =
3332+
lock_in_htlc_for_static_invoice(&static_invoice_om, peer_node_id, sender, sender_lsp);
3333+
3334+
// Check that we actually had to fall back to a 1-hop path.
3335+
assert!(sender_lsp.message_router.create_blinded_paths_res_override.lock().unwrap().0 > 0);
3336+
sender_lsp.message_router.create_blinded_paths_res_override.lock().unwrap().1 = None;
3337+
3338+
sender_lsp.node.process_pending_htlc_forwards();
3339+
let (peer_id, held_htlc_om) =
3340+
extract_held_htlc_available_oms(sender, &[sender_lsp, invoice_server, recipient])
3341+
.pop()
3342+
.unwrap();
3343+
recipient.onion_messenger.handle_onion_message(peer_id, &held_htlc_om);
3344+
3345+
// The release_htlc OM should go straight to the sender's LSP since they created a 1-hop blinded
3346+
// path to themselves for receiving it.
3347+
let release_htlc_om = recipient
3348+
.onion_messenger
3349+
.next_onion_message_for_peer(sender_lsp.node.get_our_node_id())
3350+
.unwrap();
3351+
sender_lsp
3352+
.onion_messenger
3353+
.handle_onion_message(recipient.node.get_our_node_id(), &release_htlc_om);
3354+
3355+
sender_lsp.node.process_pending_htlc_forwards();
3356+
let mut events = sender_lsp.node.get_and_clear_pending_msg_events();
3357+
assert_eq!(events.len(), 1);
3358+
let ev = remove_first_msg_event_to_node(&invoice_server.node.get_our_node_id(), &mut events);
3359+
check_added_monitors!(sender_lsp, 1);
3360+
3361+
let path: &[&Node] = &[invoice_server, recipient];
3362+
let args = PassAlongPathArgs::new(sender_lsp, path, amt_msat, payment_hash, ev);
3363+
let claimable_ev = do_pass_along_path(args).unwrap();
3364+
3365+
let route: &[&[&Node]] = &[&[sender_lsp, invoice_server, recipient]];
3366+
let keysend_preimage = extract_payment_preimage(&claimable_ev);
3367+
let (res, _) =
3368+
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
3369+
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
3370+
}
3371+
32883372
#[test]
32893373
fn fail_held_htlcs_when_cfg_unset() {
32903374
// Test that if we receive a held HTLC but `UserConfig::enable_htlc_hold` is unset, we will fail

lightning/src/offers/flow.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1215,9 +1215,14 @@ where
12151215
where
12161216
ES::Target: EntropySource,
12171217
{
1218-
// In the future, we should support multi-hop paths here.
12191218
let context =
12201219
MessageContext::AsyncPayments(AsyncPaymentsContext::ReleaseHeldHtlc { intercept_id });
1220+
if let Ok(mut paths) = self.create_blinded_paths(context.clone()) {
1221+
if let Some(path) = paths.pop() {
1222+
return path;
1223+
}
1224+
}
1225+
12211226
let num_dummy_hops = PADDED_PATH_LENGTH.saturating_sub(1);
12221227
BlindedMessagePath::new_with_dummy_hops(
12231228
&[],

lightning/src/util/test_utils.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,9 @@ pub enum TestMessageRouterInternal<'a> {
334334
pub struct TestMessageRouter<'a> {
335335
pub inner: TestMessageRouterInternal<'a>,
336336
pub peers_override: Mutex<Vec<PublicKey>>,
337+
// An override result for Self::create_blinded_paths, plus a tracker for how many times the
338+
// overridden result has been returned.
339+
pub create_blinded_paths_res_override: Mutex<(u8, Option<Result<Vec<BlindedMessagePath>, ()>>)>,
337340
}
338341

339342
impl<'a> TestMessageRouter<'a> {
@@ -346,6 +349,7 @@ impl<'a> TestMessageRouter<'a> {
346349
entropy_source,
347350
)),
348351
peers_override: Mutex::new(Vec::new()),
352+
create_blinded_paths_res_override: Mutex::new((0, None)),
349353
}
350354
}
351355

@@ -358,6 +362,7 @@ impl<'a> TestMessageRouter<'a> {
358362
entropy_source,
359363
)),
360364
peers_override: Mutex::new(Vec::new()),
365+
create_blinded_paths_res_override: Mutex::new((0, None)),
361366
}
362367
}
363368
}
@@ -385,6 +390,15 @@ impl<'a> MessageRouter for TestMessageRouter<'a> {
385390
&self, recipient: PublicKey, local_node_receive_key: ReceiveAuthKey,
386391
context: MessageContext, peers: Vec<MessageForwardNode>, secp_ctx: &Secp256k1<T>,
387392
) -> Result<Vec<BlindedMessagePath>, ()> {
393+
{
394+
let mut res_override = self.create_blinded_paths_res_override.lock().unwrap();
395+
if let Some(res) = &res_override.1 {
396+
let res = res.clone();
397+
res_override.0 += 1;
398+
return res;
399+
}
400+
}
401+
388402
let mut peers = peers;
389403
{
390404
let peers_override = self.peers_override.lock().unwrap();

0 commit comments

Comments
 (0)