Skip to content

Commit c37392e

Browse files
committed
Fix async release before HTLC decode
Handle `ReleaseHeldHtlc` messages that arrive before the sender-side LSP has even queued the held HTLC for onion decoding. Unlike #4106, which covers releases arriving after the HTLC is in `decode_update_add_htlcs` but before it reaches `pending_intercepted_htlcs`, this preserves releases that arrive one step earlier and would otherwise be dropped as HTLC not found. Co-Authored-By: HAL 9000 Signed-off-by: Elias Rohrer <[email protected]>
1 parent 9d0447e commit c37392e

File tree

2 files changed

+289
-103
lines changed

2 files changed

+289
-103
lines changed

lightning/src/ln/async_payments_tests.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,6 +2879,7 @@ fn async_payment_e2e() {
28792879
.unwrap();
28802880
let (peer_node_id, static_invoice_om, static_invoice) =
28812881
extract_static_invoice_om(invoice_server, &[sender_lsp, sender]);
2882+
28822883
let payment_hash =
28832884
lock_in_htlc_for_static_invoice(&static_invoice_om, peer_node_id, sender, sender_lsp);
28842885

@@ -2965,6 +2966,167 @@ fn async_payment_e2e() {
29652966
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
29662967
}
29672968

2969+
#[test]
2970+
fn async_payment_e2e_release_before_hold_registered() {
2971+
let chanmon_cfgs = create_chanmon_cfgs(4);
2972+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
2973+
2974+
let (sender_cfg, recipient_cfg) = (often_offline_node_cfg(), often_offline_node_cfg());
2975+
let mut sender_lsp_cfg = test_default_channel_config();
2976+
sender_lsp_cfg.enable_htlc_hold = true;
2977+
let mut invoice_server_cfg = test_default_channel_config();
2978+
invoice_server_cfg.accept_forwards_to_priv_channels = true;
2979+
2980+
let node_chanmgrs = create_node_chanmgrs(
2981+
4,
2982+
&node_cfgs,
2983+
&[Some(sender_cfg), Some(sender_lsp_cfg), Some(invoice_server_cfg), Some(recipient_cfg)],
2984+
);
2985+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
2986+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2987+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2988+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
2989+
unify_blockheight_across_nodes(&nodes);
2990+
let sender = &nodes[0];
2991+
let sender_lsp = &nodes[1];
2992+
let invoice_server = &nodes[2];
2993+
let recipient = &nodes[3];
2994+
2995+
let recipient_id = vec![42; 32];
2996+
let inv_server_paths =
2997+
invoice_server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
2998+
recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
2999+
expect_offer_paths_requests(recipient, &[invoice_server, sender_lsp]);
3000+
let invoice_flow_res =
3001+
pass_static_invoice_server_messages(invoice_server, recipient, recipient_id.clone());
3002+
let invoice = invoice_flow_res.invoice;
3003+
let invreq_path = invoice_flow_res.invoice_request_path;
3004+
3005+
let offer = recipient.node.get_async_receive_offer().unwrap();
3006+
recipient.node.peer_disconnected(invoice_server.node.get_our_node_id());
3007+
recipient.onion_messenger.peer_disconnected(invoice_server.node.get_our_node_id());
3008+
invoice_server.node.peer_disconnected(recipient.node.get_our_node_id());
3009+
invoice_server.onion_messenger.peer_disconnected(recipient.node.get_our_node_id());
3010+
3011+
let amt_msat = 5000;
3012+
let payment_id = PaymentId([1; 32]);
3013+
sender.node.pay_for_offer(&offer, Some(amt_msat), payment_id, Default::default()).unwrap();
3014+
3015+
let (peer_id, invreq_om) = extract_invoice_request_om(sender, &[sender_lsp, invoice_server]);
3016+
invoice_server.onion_messenger.handle_onion_message(peer_id, &invreq_om);
3017+
3018+
let mut events = invoice_server.node.get_and_clear_pending_events();
3019+
assert_eq!(events.len(), 1);
3020+
let (reply_path, invreq) = match events.pop().unwrap() {
3021+
Event::StaticInvoiceRequested {
3022+
recipient_id: ev_id, reply_path, invoice_request, ..
3023+
} => {
3024+
assert_eq!(recipient_id, ev_id);
3025+
(reply_path, invoice_request)
3026+
},
3027+
_ => panic!(),
3028+
};
3029+
3030+
invoice_server
3031+
.node
3032+
.respond_to_static_invoice_request(invoice, reply_path, invreq, invreq_path)
3033+
.unwrap();
3034+
let (peer_node_id, static_invoice_om, static_invoice) =
3035+
extract_static_invoice_om(invoice_server, &[sender_lsp, sender]);
3036+
3037+
// Lock the HTLC in with the sender LSP, but stop before the sender's revoke_and_ack is handed
3038+
// back to the sender LSP. This reproduces the real LSPS2 timing where ReleaseHeldHtlc can
3039+
// arrive before the held HTLC is queued for decode on the sender LSP.
3040+
sender.onion_messenger.handle_onion_message(peer_node_id, &static_invoice_om);
3041+
check_added_monitors(sender, 1);
3042+
let commitment_update = get_htlc_update_msgs(&sender, &sender_lsp.node.get_our_node_id());
3043+
let update_add = commitment_update.update_add_htlcs[0].clone();
3044+
let payment_hash = update_add.payment_hash;
3045+
assert!(update_add.hold_htlc.is_some());
3046+
sender_lsp.node.handle_update_add_htlc(sender.node.get_our_node_id(), &update_add);
3047+
sender_lsp.node.handle_commitment_signed_batch_test(
3048+
sender.node.get_our_node_id(),
3049+
&commitment_update.commitment_signed,
3050+
);
3051+
check_added_monitors(sender_lsp, 1);
3052+
let (_extra_msg_option, sender_raa, sender_holding_cell_htlcs) =
3053+
do_main_commitment_signed_dance(sender_lsp, sender, false);
3054+
assert!(sender_holding_cell_htlcs.is_empty());
3055+
3056+
let held_htlc_om_to_inv_server = sender
3057+
.onion_messenger
3058+
.next_onion_message_for_peer(invoice_server.node.get_our_node_id())
3059+
.unwrap();
3060+
invoice_server
3061+
.onion_messenger
3062+
.handle_onion_message(sender_lsp.node.get_our_node_id(), &held_htlc_om_to_inv_server);
3063+
3064+
let mut events_rc = core::cell::RefCell::new(Vec::new());
3065+
invoice_server.onion_messenger.process_pending_events(&|e| Ok(events_rc.borrow_mut().push(e)));
3066+
let events = events_rc.into_inner();
3067+
let held_htlc_om = events
3068+
.into_iter()
3069+
.find_map(|ev| {
3070+
if let Event::OnionMessageIntercepted { message, .. } = ev {
3071+
let peeled_onion = recipient.onion_messenger.peel_onion_message(&message).unwrap();
3072+
if matches!(
3073+
peeled_onion,
3074+
PeeledOnion::Offers(OffersMessage::InvoiceRequest { .. }, _, _)
3075+
) {
3076+
return None;
3077+
}
3078+
3079+
assert!(matches!(
3080+
peeled_onion,
3081+
PeeledOnion::AsyncPayments(AsyncPaymentsMessage::HeldHtlcAvailable(_), _, _)
3082+
));
3083+
Some(message)
3084+
} else {
3085+
None
3086+
}
3087+
})
3088+
.unwrap();
3089+
3090+
let mut reconnect_args = ReconnectArgs::new(invoice_server, recipient);
3091+
reconnect_args.send_channel_ready = (true, true);
3092+
reconnect_nodes(reconnect_args);
3093+
3094+
let events = core::cell::RefCell::new(Vec::new());
3095+
invoice_server.onion_messenger.process_pending_events(&|e| Ok(events.borrow_mut().push(e)));
3096+
assert_eq!(events.borrow().len(), 1);
3097+
assert!(matches!(events.into_inner().pop().unwrap(), Event::OnionMessagePeerConnected { .. }));
3098+
expect_offer_paths_requests(recipient, &[invoice_server]);
3099+
3100+
recipient
3101+
.onion_messenger
3102+
.handle_onion_message(invoice_server.node.get_our_node_id(), &held_htlc_om);
3103+
let (peer_id, release_htlc_om) =
3104+
extract_release_htlc_oms(recipient, &[sender, sender_lsp, invoice_server]).pop().unwrap();
3105+
sender_lsp.onion_messenger.handle_onion_message(peer_id, &release_htlc_om);
3106+
3107+
// Now let the sender LSP receive the sender's revoke_and_ack and continue processing the held
3108+
// HTLC. Old code drops the release request above; fixed code remembers it and forwards the HTLC.
3109+
sender_lsp.node.handle_revoke_and_ack(sender.node.get_our_node_id(), &sender_raa);
3110+
check_added_monitors(sender_lsp, 1);
3111+
assert!(sender_lsp.node.get_and_clear_pending_msg_events().is_empty());
3112+
sender_lsp.node.process_pending_htlc_forwards();
3113+
let mut events = sender_lsp.node.get_and_clear_pending_msg_events();
3114+
assert_eq!(events.len(), 1);
3115+
let ev = remove_first_msg_event_to_node(&invoice_server.node.get_our_node_id(), &mut events);
3116+
check_added_monitors(&sender_lsp, 1);
3117+
3118+
let path: &[&Node] = &[invoice_server, recipient];
3119+
let args = PassAlongPathArgs::new(sender_lsp, path, amt_msat, payment_hash, ev)
3120+
.with_dummy_tlvs(&[DummyTlvs::default(); DEFAULT_PAYMENT_DUMMY_HOPS]);
3121+
let claimable_ev = do_pass_along_path(args).unwrap();
3122+
3123+
let route: &[&[&Node]] = &[&[sender_lsp, invoice_server, recipient]];
3124+
let keysend_preimage = extract_payment_preimage(&claimable_ev);
3125+
let (res, _) =
3126+
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
3127+
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
3128+
}
3129+
29683130
#[test]
29693131
fn held_htlc_timeout() {
29703132
// Test that if a held HTLC doesn't get released for a long time, it will eventually time out and

0 commit comments

Comments
 (0)