Skip to content

Commit 02eb665

Browse files
WIP Test async send
1 parent 35fe23a commit 02eb665

File tree

2 files changed

+215
-11
lines changed

2 files changed

+215
-11
lines changed

lightning/src/ln/async_payments_tests.rs

Lines changed: 207 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
// You may not use this file except in accordance with one or both of these
88
// licenses.
99

10-
use crate::blinded_path::message::{MessageContext, OffersContext};
10+
use crate::blinded_path::message::{MessageContext, NextMessageHop, OffersContext};
1111
use crate::blinded_path::payment::PaymentContext;
1212
use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentTlvs};
1313
use crate::chain::channelmonitor::{HTLC_FAIL_BACK_BUFFER, LATENCY_GRACE_PERIOD_BLOCKS};
@@ -54,11 +54,12 @@ use crate::sign::NodeSigner;
5454
use crate::sync::Mutex;
5555
use crate::types::features::Bolt12InvoiceFeatures;
5656
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
57+
use crate::util::config::UserConfig;
5758
use crate::util::ser::Writeable;
5859
use bitcoin::constants::ChainHash;
5960
use bitcoin::network::Network;
6061
use bitcoin::secp256k1;
61-
use bitcoin::secp256k1::Secp256k1;
62+
use bitcoin::secp256k1::{PublicKey, Secp256k1};
6263

6364
use core::convert::Infallible;
6465
use core::time::Duration;
@@ -331,32 +332,114 @@ fn expect_offer_paths_requests(recipient: &Node, next_hop_nodes: &[&Node]) {
331332
// We want to check that the async recipient has enqueued at least one `OfferPathsRequest` and no
332333
// other message types. Check this by iterating through all their outbound onion messages, peeling
333334
// multiple times if the messages are forwarded through other nodes.
334-
let per_msg_recipient_msgs = recipient.onion_messenger.release_pending_msgs();
335+
let offer_paths_reqs = extract_expected_om(recipient, next_hop_nodes, |peeled_onion| {
336+
matches!(
337+
peeled_onion,
338+
PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _)
339+
)
340+
});
341+
assert!(!offer_paths_reqs.is_empty());
342+
}
343+
344+
fn extract_invoice_request_om<'a>(
345+
payer: &'a Node, next_hop_nodes: &[&'a Node],
346+
) -> (PublicKey, msgs::OnionMessage) {
347+
extract_expected_om(payer, next_hop_nodes, |peeled_onion| {
348+
matches!(peeled_onion, &PeeledOnion::Offers(OffersMessage::InvoiceRequest(_), _, _))
349+
})
350+
.pop()
351+
.unwrap()
352+
}
353+
354+
fn extract_static_invoice_om<'a>(
355+
invoice_server: &'a Node, next_hop_nodes: &[&'a Node],
356+
) -> (PublicKey, msgs::OnionMessage, StaticInvoice) {
357+
let mut static_invoice = None;
358+
let (peer_id, om) = extract_expected_om(invoice_server, next_hop_nodes, |peeled_onion| {
359+
if let &PeeledOnion::Offers(OffersMessage::StaticInvoice(inv), _, _) = &peeled_onion {
360+
static_invoice = Some(inv.clone());
361+
true
362+
} else {
363+
false
364+
}
365+
})
366+
.pop()
367+
.unwrap();
368+
(peer_id, om, static_invoice.unwrap())
369+
}
370+
371+
fn extract_held_htlc_available_om<'a>(
372+
payer: &'a Node, next_hop_nodes: &[&'a Node],
373+
) -> (PublicKey, msgs::OnionMessage) {
374+
extract_expected_om(payer, next_hop_nodes, |peeled_onion| {
375+
matches!(
376+
peeled_onion,
377+
&PeeledOnion::AsyncPayments(AsyncPaymentsMessage::HeldHtlcAvailable(_), _, _)
378+
)
379+
})
380+
.pop()
381+
.unwrap()
382+
}
383+
384+
fn extract_release_htlc_om<'a>(
385+
recipient: &'a Node, next_hop_nodes: &[&'a Node],
386+
) -> (PublicKey, msgs::OnionMessage) {
387+
extract_expected_om(recipient, next_hop_nodes, |peeled_onion| {
388+
matches!(
389+
peeled_onion,
390+
&PeeledOnion::AsyncPayments(AsyncPaymentsMessage::ReleaseHeldHtlc(_), _, _)
391+
)
392+
})
393+
.pop()
394+
.unwrap()
395+
}
396+
397+
fn extract_expected_om<F>(
398+
msg_sender: &Node, next_hop_nodes: &[&Node], mut expected_msg_type: F,
399+
) -> Vec<(PublicKey, msgs::OnionMessage)>
400+
where
401+
F: FnMut(&PeeledOnion<Infallible>) -> bool,
402+
{
403+
let per_msg_recipient_msgs = msg_sender.onion_messenger.release_pending_msgs();
335404
let mut pk_to_msg = Vec::new();
336405
for (pk, msgs) in per_msg_recipient_msgs {
337406
for msg in msgs {
338407
pk_to_msg.push((pk, msg));
339408
}
340409
}
341-
let mut num_offer_paths_reqs: u8 = 0;
410+
let mut msgs = Vec::new();
342411
while let Some((pk, msg)) = pk_to_msg.pop() {
343412
let node = next_hop_nodes.iter().find(|node| node.node.get_our_node_id() == pk).unwrap();
344413
let peeled_msg = node.onion_messenger.peel_onion_message(&msg).unwrap();
345414
match peeled_msg {
346-
PeeledOnion::AsyncPayments(AsyncPaymentsMessage::OfferPathsRequest(_), _, _) => {
347-
num_offer_paths_reqs += 1;
348-
},
349415
PeeledOnion::Forward(next_hop, msg) => {
350416
let next_pk = match next_hop {
351-
crate::blinded_path::message::NextMessageHop::NodeId(pk) => pk,
352-
_ => panic!(),
417+
NextMessageHop::NodeId(pk) => pk,
418+
NextMessageHop::ShortChannelId(scid) => {
419+
let mut next_pk = None;
420+
for node in next_hop_nodes {
421+
if node.node.get_our_node_id() == pk {
422+
continue;
423+
}
424+
for channel in node.node.list_channels() {
425+
if channel.short_channel_id.unwrap() == scid
426+
|| channel.inbound_scid_alias.unwrap_or(0) == scid
427+
{
428+
next_pk = Some(node.node.get_our_node_id());
429+
}
430+
}
431+
}
432+
next_pk.unwrap()
433+
},
353434
};
354435
pk_to_msg.push((next_pk, msg));
355436
},
356-
_ => panic!("Unexpected message"),
437+
peeled_onion if expected_msg_type(&peeled_onion) => msgs.push((pk, msg)),
438+
peeled_onion => panic!("Unexpected message: {:#?}", peeled_onion),
357439
}
358440
}
359-
assert!(num_offer_paths_reqs > 0);
441+
assert!(!msgs.is_empty());
442+
msgs
360443
}
361444

362445
fn advance_time_by(duration: Duration, node: &Node) {
@@ -365,6 +448,14 @@ fn advance_time_by(duration: Duration, node: &Node) {
365448
connect_block(node, &block);
366449
}
367450

451+
fn often_offline_node_cfg() -> UserConfig {
452+
let mut cfg = test_default_channel_config();
453+
cfg.channel_handshake_config.announce_for_forwarding = false;
454+
cfg.channel_handshake_limits.force_announced_channel_preference = true;
455+
cfg.send_payments_async = true;
456+
cfg
457+
}
458+
368459
#[test]
369460
fn invalid_keysend_payment_secret() {
370461
let chanmon_cfgs = create_chanmon_cfgs(3);
@@ -2206,3 +2297,108 @@ fn invoice_server_is_not_channel_peer() {
22062297
let res = claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
22072298
assert_eq!(res.0, Some(PaidBolt12Invoice::StaticInvoice(invoice)));
22082299
}
2300+
2301+
#[test]
2302+
fn simple_async_sender() {
2303+
// Test the basic case of an async sender paying an async recipient.
2304+
let chanmon_cfgs = create_chanmon_cfgs(4);
2305+
let node_cfgs = create_node_cfgs(4, &chanmon_cfgs);
2306+
let (sender_cfg, recipient_cfg) = (often_offline_node_cfg(), often_offline_node_cfg());
2307+
let mut invoice_server_cfg = test_default_channel_config();
2308+
invoice_server_cfg.accept_forwards_to_priv_channels = true;
2309+
let node_chanmgrs = create_node_chanmgrs(
2310+
4,
2311+
&node_cfgs,
2312+
&[Some(sender_cfg), None, Some(invoice_server_cfg), Some(recipient_cfg)],
2313+
);
2314+
let nodes = create_network(4, &node_cfgs, &node_chanmgrs);
2315+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
2316+
create_announced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
2317+
create_unannounced_chan_between_nodes_with_value(&nodes, 2, 3, 1_000_000, 0);
2318+
// Make sure all nodes are at the same block height
2319+
let node_max_height =
2320+
nodes.iter().map(|node| node.blocks.lock().unwrap().len()).max().unwrap() as u32;
2321+
connect_blocks(&nodes[0], node_max_height - nodes[0].best_block_info().1);
2322+
connect_blocks(&nodes[1], node_max_height - nodes[1].best_block_info().1);
2323+
connect_blocks(&nodes[2], node_max_height - nodes[2].best_block_info().1);
2324+
connect_blocks(&nodes[3], node_max_height - nodes[3].best_block_info().1);
2325+
let sender = &nodes[0];
2326+
let sender_lsp = &nodes[1];
2327+
let invoice_server = &nodes[2];
2328+
let recipient = &nodes[3];
2329+
2330+
let recipient_id = vec![42; 32];
2331+
let inv_server_paths =
2332+
invoice_server.node.blinded_paths_for_async_recipient(recipient_id.clone(), None).unwrap();
2333+
recipient.node.set_paths_to_static_invoice_server(inv_server_paths).unwrap();
2334+
expect_offer_paths_requests(recipient, &[sender, sender_lsp, invoice_server]);
2335+
let invoice =
2336+
pass_static_invoice_server_messages(invoice_server, recipient, recipient_id.clone())
2337+
.invoice;
2338+
2339+
let offer = recipient.node.get_async_receive_offer().unwrap();
2340+
let amt_msat = 5000;
2341+
let payment_id = PaymentId([1; 32]);
2342+
let params = RouteParametersConfig::default();
2343+
sender
2344+
.node
2345+
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), params)
2346+
.unwrap();
2347+
2348+
// Forward invreq to server, pass static invoice back, check that htlc was locked in/monitor was
2349+
// added
2350+
let (peer_id, invreq_om) = extract_invoice_request_om(sender, &[sender_lsp, invoice_server]);
2351+
invoice_server.onion_messenger.handle_onion_message(peer_id, &invreq_om);
2352+
2353+
let mut events = invoice_server.node.get_and_clear_pending_events();
2354+
assert_eq!(events.len(), 1);
2355+
let reply_path = match events.pop().unwrap() {
2356+
Event::StaticInvoiceRequested { recipient_id: ev_id, invoice_slot: _, reply_path } => {
2357+
assert_eq!(recipient_id, ev_id);
2358+
reply_path
2359+
},
2360+
_ => panic!(),
2361+
};
2362+
2363+
invoice_server.node.send_static_invoice(invoice, reply_path).unwrap();
2364+
let (peer_node_id, static_invoice_om, static_invoice) =
2365+
extract_static_invoice_om(invoice_server, &[sender_lsp, sender, recipient]);
2366+
2367+
// The sender should lock in the held HTLC with their LSP right after receiving the static invoice.
2368+
sender.onion_messenger.handle_onion_message(peer_node_id, &static_invoice_om);
2369+
check_added_monitors(sender, 1);
2370+
let commitment_update = get_htlc_update_msgs!(sender, sender_lsp.node.get_our_node_id());
2371+
let update_add = commitment_update.update_add_htlcs[0].clone();
2372+
let payment_hash = update_add.payment_hash;
2373+
assert!(update_add.hold_htlc.is_some());
2374+
sender_lsp.node.handle_update_add_htlc(sender.node.get_our_node_id(), &update_add);
2375+
commitment_signed_dance!(sender_lsp, sender, &commitment_update.commitment_signed, false, true);
2376+
2377+
// Ensure that after the held HTLC is locked in, the sender's lsp does not forward it immediately.
2378+
sender_lsp.node.process_pending_htlc_forwards();
2379+
assert!(sender_lsp.node.get_and_clear_pending_msg_events().is_empty());
2380+
2381+
let (peer_id, held_htlc_om) =
2382+
extract_held_htlc_available_om(sender, &[sender_lsp, invoice_server, recipient]);
2383+
recipient.onion_messenger.handle_onion_message(peer_id, &held_htlc_om);
2384+
let (peer_id, release_htlc_om) =
2385+
extract_release_htlc_om(recipient, &[sender, sender_lsp, invoice_server]);
2386+
sender_lsp.onion_messenger.handle_onion_message(peer_id, &release_htlc_om);
2387+
2388+
// After the sender's LSP receives release_held_htlc from the recipient, the payment can complete
2389+
sender_lsp.node.process_pending_htlc_forwards();
2390+
let mut events = sender_lsp.node.get_and_clear_pending_msg_events();
2391+
assert_eq!(events.len(), 1);
2392+
let ev = remove_first_msg_event_to_node(&invoice_server.node.get_our_node_id(), &mut events);
2393+
check_added_monitors!(sender_lsp, 1);
2394+
2395+
let path: &[&Node] = &[invoice_server, recipient];
2396+
let args = PassAlongPathArgs::new(sender_lsp, path, amt_msat, payment_hash, ev);
2397+
let claimable_ev = do_pass_along_path(args).unwrap();
2398+
2399+
let route: &[&[&Node]] = &[&[sender_lsp, invoice_server, recipient]];
2400+
let keysend_preimage = extract_payment_preimage(&claimable_ev);
2401+
let (res, _) =
2402+
claim_payment_along_route(ClaimAlongRouteArgs::new(sender, route, keysend_preimage));
2403+
assert_eq!(res, Some(PaidBolt12Invoice::StaticInvoice(static_invoice)));
2404+
}

lightning/src/ln/channelmanager.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14947,6 +14947,11 @@ where
1494714947
pub(crate) fn provided_node_features(config: &UserConfig) -> NodeFeatures {
1494814948
let mut node_features = provided_init_features(config).to_context();
1494914949
node_features.set_keysend_optional();
14950+
14951+
// XXX only set this if we are private node by config and in init_features
14952+
#[cfg(test)]
14953+
node_features.set_htlc_hold_optional();
14954+
1495014955
node_features
1495114956
}
1495214957

@@ -15016,6 +15021,9 @@ pub fn provided_init_features(config: &UserConfig) -> InitFeatures {
1501615021
features.set_anchor_zero_fee_commitments_optional();
1501715022
}
1501815023

15024+
#[cfg(test)]
15025+
features.set_htlc_hold_optional();
15026+
1501915027
features
1502015028
}
1502115029

0 commit comments

Comments
 (0)