Skip to content

Commit ba4a13e

Browse files
f move async payments tests into their own file
1 parent 9a52c80 commit ba4a13e

File tree

3 files changed

+254
-232
lines changed

3 files changed

+254
-232
lines changed
+251
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
// This file is Copyright its original authors, visible in version control
2+
// history.
3+
//
4+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
5+
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
7+
// You may not use this file except in accordance with one or both of these
8+
// licenses.
9+
10+
use bitcoin::secp256k1::Secp256k1;
11+
use crate::blinded_path::message::{BlindedMessagePath, MessageContext, OffersContext};
12+
use crate::events::{Event, MessageSendEventsProvider, PaymentFailureReason};
13+
use crate::ln::channelmanager::PaymentId;
14+
use crate::ln::functional_test_utils::*;
15+
use crate::ln::msgs;
16+
use crate::ln::msgs::OnionMessageHandler;
17+
use crate::ln::outbound_payment::Retry;
18+
use crate::offers::nonce::Nonce;
19+
use crate::onion_message::async_payments::{
20+
AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc,
21+
};
22+
use crate::onion_message::messenger::{
23+
Destination, MessageRouter, MessageSendInstructions, PeeledOnion,
24+
};
25+
use crate::onion_message::offers::OffersMessage;
26+
use crate::onion_message::packet::ParsedOnionMessageContents;
27+
use crate::prelude::*;
28+
use crate::types::features::Bolt12InvoiceFeatures;
29+
30+
use core::convert::Infallible;
31+
32+
#[cfg(async_payments)]
33+
fn extract_invoice_request_reply_path<'a, 'b, 'c>(
34+
invreq_recipient: &Node<'a, 'b, 'c>, message: &msgs::OnionMessage
35+
) -> BlindedMessagePath {
36+
match invreq_recipient.onion_messenger.peel_onion_message(message) {
37+
Ok(PeeledOnion::Receive(invreq, context, reply_path)) => {
38+
assert!(
39+
matches!(invreq, ParsedOnionMessageContents::Offers(OffersMessage::InvoiceRequest(_)))
40+
);
41+
assert!(
42+
matches!(context, Some(MessageContext::Offers(OffersContext::InvoiceRequest { .. })))
43+
);
44+
reply_path.unwrap()
45+
},
46+
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
47+
Err(e) => panic!("Failed to process onion message {:?}", e),
48+
}
49+
}
50+
51+
#[test]
52+
#[cfg(async_payments)]
53+
fn static_invoice_unknown_required_features() {
54+
// Test that we will fail to pay a static invoice with unsupported required features.
55+
let secp_ctx = Secp256k1::new();
56+
let chanmon_cfgs = create_chanmon_cfgs(3);
57+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
58+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
59+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
60+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
61+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
62+
63+
let blinded_paths_to_always_online_node = nodes[1].message_router.create_blinded_paths(
64+
nodes[1].node.get_our_node_id(),
65+
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
66+
Vec::new(), &secp_ctx
67+
).unwrap();
68+
let (offer_builder, nonce) =
69+
nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node).unwrap();
70+
let offer = offer_builder.build().unwrap();
71+
let static_invoice_unknown_req_features = nodes[2].node.create_static_invoice_builder(
72+
&offer, nonce, None
73+
)
74+
.unwrap()
75+
.features_unchecked(Bolt12InvoiceFeatures::unknown())
76+
.build_and_sign(&secp_ctx).unwrap();
77+
78+
let amt_msat = 5000;
79+
let payment_id = PaymentId([1; 32]);
80+
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();
81+
82+
// Don't forward the invreq since we don't support retrieving the static invoice from the
83+
// recipient's LSP yet, instead manually construct the response.
84+
let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
85+
let invreq_reply_path = extract_invoice_request_reply_path(&nodes[1], &invreq_om);
86+
nodes[1].onion_messenger.send_onion_message(
87+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(static_invoice_unknown_req_features)),
88+
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path) }
89+
).unwrap();
90+
91+
let static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
92+
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
93+
let events = nodes[0].node.get_and_clear_pending_events();
94+
assert_eq!(events.len(), 1);
95+
match events[0] {
96+
Event::PaymentFailed { payment_hash, payment_id: ev_payment_id, reason } => {
97+
assert_eq!(payment_hash, None);
98+
assert_eq!(payment_id, ev_payment_id);
99+
assert_eq!(reason, Some(PaymentFailureReason::UnknownRequiredFeatures));
100+
},
101+
_ => panic!()
102+
}
103+
}
104+
105+
#[test]
106+
fn ignore_unexpected_static_invoice() {
107+
// Test that we'll ignore unexpected static invoices, invoices that don't match our invoice
108+
// request, and duplicate invoices.
109+
let secp_ctx = Secp256k1::new();
110+
let chanmon_cfgs = create_chanmon_cfgs(3);
111+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
112+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
113+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
114+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
115+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
116+
117+
// Initiate payment to the sender's intended offer.
118+
let blinded_paths_to_always_online_node = nodes[1].message_router.create_blinded_paths(
119+
nodes[1].node.get_our_node_id(),
120+
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
121+
Vec::new(), &secp_ctx
122+
).unwrap();
123+
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node.clone()).unwrap();
124+
let offer = offer_builder.build().unwrap();
125+
let amt_msat = 5000;
126+
let payment_id = PaymentId([1; 32]);
127+
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();
128+
129+
// Don't forward the invreq since we don't support retrieving the static invoice from the
130+
// recipient's LSP yet, instead manually construct the responses below.
131+
let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
132+
let invreq_reply_path = extract_invoice_request_reply_path(&nodes[1], &invreq_om);
133+
134+
// Create a static invoice to be sent over the reply path containing the original payment_id, but
135+
// the static invoice corresponds to a different offer than was originally paid.
136+
let unexpected_static_invoice = {
137+
let (offer_builder, nonce) = nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node).unwrap();
138+
let sender_unintended_offer = offer_builder.build().unwrap();
139+
140+
nodes[2].node.create_static_invoice_builder(
141+
&sender_unintended_offer, nonce, None
142+
).unwrap().build_and_sign(&secp_ctx).unwrap()
143+
};
144+
145+
// Check that we'll ignore the unexpected static invoice.
146+
nodes[1].onion_messenger.send_onion_message(
147+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(unexpected_static_invoice)),
148+
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path.clone()) }
149+
).unwrap();
150+
let unexpected_static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
151+
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &unexpected_static_invoice_om);
152+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
153+
assert!(async_pmts_msgs.is_empty());
154+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
155+
156+
// A valid static invoice corresponding to the correct offer will succeed and cause us to send a
157+
// held_htlc_available onion message.
158+
let valid_static_invoice = nodes[2].node.create_static_invoice_builder(
159+
&offer, offer_nonce, None
160+
).unwrap().build_and_sign(&secp_ctx).unwrap();
161+
162+
nodes[1].onion_messenger.send_onion_message(
163+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(valid_static_invoice.clone())),
164+
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path.clone()) }
165+
).unwrap();
166+
let static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
167+
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
168+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
169+
assert!(!async_pmts_msgs.is_empty());
170+
assert!(async_pmts_msgs.into_iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
171+
172+
// Receiving a duplicate invoice will have no effect.
173+
nodes[1].onion_messenger.send_onion_message(
174+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(valid_static_invoice)),
175+
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path) }
176+
).unwrap();
177+
let dup_static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
178+
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &dup_static_invoice_om);
179+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
180+
assert!(async_pmts_msgs.is_empty());
181+
}
182+
183+
#[test]
184+
fn pays_static_invoice() {
185+
// Test that we support the async payments flow up to and including sending the actual payment.
186+
// Async receive is not yet supported so we don't complete the payment yet.
187+
let secp_ctx = Secp256k1::new();
188+
let chanmon_cfgs = create_chanmon_cfgs(3);
189+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
190+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
191+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
192+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
193+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
194+
195+
let blinded_paths_to_always_online_node = nodes[1].message_router.create_blinded_paths(
196+
nodes[1].node.get_our_node_id(),
197+
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
198+
Vec::new(), &secp_ctx
199+
).unwrap();
200+
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(blinded_paths_to_always_online_node).unwrap();
201+
let offer = offer_builder.build().unwrap();
202+
let amt_msat = 5000;
203+
let payment_id = PaymentId([1; 32]);
204+
let static_invoice = nodes[2].node.create_static_invoice_builder(
205+
&offer, offer_nonce, None
206+
).unwrap().build_and_sign(&secp_ctx).unwrap();
207+
208+
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();
209+
210+
// Don't forward the invreq since we don't support retrieving the static invoice from the
211+
// recipient's LSP yet, instead manually construct the response.
212+
let invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
213+
let invreq_reply_path = extract_invoice_request_reply_path(&nodes[1], &invreq_om);
214+
215+
nodes[1].onion_messenger.send_onion_message(
216+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(static_invoice)),
217+
MessageSendInstructions::WithoutReplyPath { destination: Destination::BlindedPath(invreq_reply_path) }
218+
).unwrap();
219+
let static_invoice_om = nodes[1].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
220+
nodes[0].onion_messenger.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
221+
let mut async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
222+
assert!(!async_pmts_msgs.is_empty());
223+
assert!(async_pmts_msgs.iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
224+
225+
// Manually send the message and context releasing the HTLC since the recipient doesn't support
226+
// responding themselves yet.
227+
let held_htlc_avail_reply_path = match async_pmts_msgs.pop().unwrap().1 {
228+
MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => reply_path,
229+
_ => panic!()
230+
};
231+
nodes[2].onion_messenger.send_onion_message(
232+
ParsedOnionMessageContents::<Infallible>::AsyncPayments(
233+
AsyncPaymentsMessage::ReleaseHeldHtlc(ReleaseHeldHtlc {}),
234+
),
235+
MessageSendInstructions::WithoutReplyPath {
236+
destination: Destination::BlindedPath(held_htlc_avail_reply_path)
237+
}
238+
).unwrap();
239+
240+
let release_held_htlc_om = nodes[2].onion_messenger.next_onion_message_for_peer(nodes[0].node.get_our_node_id()).unwrap();
241+
nodes[0].onion_messenger.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);
242+
243+
// Check that we've queued the HTLCs of the async keysend payment.
244+
let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
245+
assert_eq!(htlc_updates.update_add_htlcs.len(), 1);
246+
check_added_monitors!(nodes[0], 1);
247+
248+
// Receiving a duplicate release_htlc message doesn't result in duplicate payment.
249+
nodes[0].onion_messenger.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);
250+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
251+
}

0 commit comments

Comments
 (0)