Skip to content

Commit 633a08d

Browse files
Test failures on paying static invoices
Since adding support for creating static invoices from ChannelManager, it's easier to test these failure cases that went untested when we added support for paying static invoices.
1 parent f9ca1a1 commit 633a08d

File tree

3 files changed

+359
-1
lines changed

3 files changed

+359
-1
lines changed
+355
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,355 @@
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 crate::blinded_path::message::{MessageContext, OffersContext};
11+
use crate::events::{Event, MessageSendEventsProvider, PaymentFailureReason};
12+
use crate::ln::channelmanager::PaymentId;
13+
use crate::ln::functional_test_utils::*;
14+
use crate::ln::msgs::OnionMessageHandler;
15+
use crate::ln::offers_tests;
16+
use crate::ln::outbound_payment::Retry;
17+
use crate::offers::nonce::Nonce;
18+
use crate::onion_message::async_payments::{
19+
AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc,
20+
};
21+
use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions};
22+
use crate::onion_message::offers::OffersMessage;
23+
use crate::onion_message::packet::ParsedOnionMessageContents;
24+
use crate::prelude::*;
25+
use crate::types::features::Bolt12InvoiceFeatures;
26+
use bitcoin::secp256k1::Secp256k1;
27+
28+
use core::convert::Infallible;
29+
30+
#[test]
31+
#[cfg(async_payments)]
32+
fn static_invoice_unknown_required_features() {
33+
// Test that we will fail to pay a static invoice with unsupported required features.
34+
let secp_ctx = Secp256k1::new();
35+
let chanmon_cfgs = create_chanmon_cfgs(3);
36+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
37+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
38+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
39+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
40+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
41+
42+
let blinded_paths_to_always_online_node = nodes[1]
43+
.message_router
44+
.create_blinded_paths(
45+
nodes[1].node.get_our_node_id(),
46+
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
47+
Vec::new(),
48+
&secp_ctx,
49+
)
50+
.unwrap();
51+
let (offer_builder, nonce) = nodes[2]
52+
.node
53+
.create_async_receive_offer_builder(blinded_paths_to_always_online_node)
54+
.unwrap();
55+
let offer = offer_builder.build().unwrap();
56+
let static_invoice_unknown_req_features = nodes[2]
57+
.node
58+
.create_static_invoice_builder(&offer, nonce, None)
59+
.unwrap()
60+
.features_unchecked(Bolt12InvoiceFeatures::unknown())
61+
.build_and_sign(&secp_ctx)
62+
.unwrap();
63+
64+
let amt_msat = 5000;
65+
let payment_id = PaymentId([1; 32]);
66+
nodes[0]
67+
.node
68+
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None)
69+
.unwrap();
70+
71+
// Don't forward the invreq since we don't support retrieving the static invoice from the
72+
// recipient's LSP yet, instead manually construct the response.
73+
let invreq_om = nodes[0]
74+
.onion_messenger
75+
.next_onion_message_for_peer(nodes[1].node.get_our_node_id())
76+
.unwrap();
77+
let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1;
78+
nodes[1]
79+
.onion_messenger
80+
.send_onion_message(
81+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(
82+
static_invoice_unknown_req_features,
83+
)),
84+
MessageSendInstructions::WithoutReplyPath {
85+
destination: Destination::BlindedPath(invreq_reply_path),
86+
},
87+
)
88+
.unwrap();
89+
90+
let static_invoice_om = nodes[1]
91+
.onion_messenger
92+
.next_onion_message_for_peer(nodes[0].node.get_our_node_id())
93+
.unwrap();
94+
nodes[0]
95+
.onion_messenger
96+
.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
97+
let events = nodes[0].node.get_and_clear_pending_events();
98+
assert_eq!(events.len(), 1);
99+
match events[0] {
100+
Event::PaymentFailed { payment_hash, payment_id: ev_payment_id, reason } => {
101+
assert_eq!(payment_hash, None);
102+
assert_eq!(payment_id, ev_payment_id);
103+
assert_eq!(reason, Some(PaymentFailureReason::UnknownRequiredFeatures));
104+
},
105+
_ => panic!(),
106+
}
107+
}
108+
109+
#[test]
110+
fn ignore_unexpected_static_invoice() {
111+
// Test that we'll ignore unexpected static invoices, invoices that don't match our invoice
112+
// request, and duplicate invoices.
113+
let secp_ctx = Secp256k1::new();
114+
let chanmon_cfgs = create_chanmon_cfgs(3);
115+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
116+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
117+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
118+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
119+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
120+
121+
// Initiate payment to the sender's intended offer.
122+
let blinded_paths_to_always_online_node = nodes[1]
123+
.message_router
124+
.create_blinded_paths(
125+
nodes[1].node.get_our_node_id(),
126+
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
127+
Vec::new(),
128+
&secp_ctx,
129+
)
130+
.unwrap();
131+
let (offer_builder, offer_nonce) = nodes[2]
132+
.node
133+
.create_async_receive_offer_builder(blinded_paths_to_always_online_node.clone())
134+
.unwrap();
135+
let offer = offer_builder.build().unwrap();
136+
let amt_msat = 5000;
137+
let payment_id = PaymentId([1; 32]);
138+
nodes[0]
139+
.node
140+
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None)
141+
.unwrap();
142+
143+
// Don't forward the invreq since we don't support retrieving the static invoice from the
144+
// recipient's LSP yet, instead manually construct the responses below.
145+
let invreq_om = nodes[0]
146+
.onion_messenger
147+
.next_onion_message_for_peer(nodes[1].node.get_our_node_id())
148+
.unwrap();
149+
let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1;
150+
151+
// Create a static invoice to be sent over the reply path containing the original payment_id, but
152+
// the static invoice corresponds to a different offer than was originally paid.
153+
let unexpected_static_invoice = {
154+
let (offer_builder, nonce) = nodes[2]
155+
.node
156+
.create_async_receive_offer_builder(blinded_paths_to_always_online_node)
157+
.unwrap();
158+
let sender_unintended_offer = offer_builder.build().unwrap();
159+
160+
nodes[2]
161+
.node
162+
.create_static_invoice_builder(&sender_unintended_offer, nonce, None)
163+
.unwrap()
164+
.build_and_sign(&secp_ctx)
165+
.unwrap()
166+
};
167+
168+
// Check that we'll ignore the unexpected static invoice.
169+
nodes[1]
170+
.onion_messenger
171+
.send_onion_message(
172+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(
173+
unexpected_static_invoice,
174+
)),
175+
MessageSendInstructions::WithoutReplyPath {
176+
destination: Destination::BlindedPath(invreq_reply_path.clone()),
177+
},
178+
)
179+
.unwrap();
180+
let unexpected_static_invoice_om = nodes[1]
181+
.onion_messenger
182+
.next_onion_message_for_peer(nodes[0].node.get_our_node_id())
183+
.unwrap();
184+
nodes[0]
185+
.onion_messenger
186+
.handle_onion_message(nodes[1].node.get_our_node_id(), &unexpected_static_invoice_om);
187+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
188+
assert!(async_pmts_msgs.is_empty());
189+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
190+
191+
// A valid static invoice corresponding to the correct offer will succeed and cause us to send a
192+
// held_htlc_available onion message.
193+
let valid_static_invoice = nodes[2]
194+
.node
195+
.create_static_invoice_builder(&offer, offer_nonce, None)
196+
.unwrap()
197+
.build_and_sign(&secp_ctx)
198+
.unwrap();
199+
200+
nodes[1]
201+
.onion_messenger
202+
.send_onion_message(
203+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(
204+
valid_static_invoice.clone(),
205+
)),
206+
MessageSendInstructions::WithoutReplyPath {
207+
destination: Destination::BlindedPath(invreq_reply_path.clone()),
208+
},
209+
)
210+
.unwrap();
211+
let static_invoice_om = nodes[1]
212+
.onion_messenger
213+
.next_onion_message_for_peer(nodes[0].node.get_our_node_id())
214+
.unwrap();
215+
nodes[0]
216+
.onion_messenger
217+
.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
218+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
219+
assert!(!async_pmts_msgs.is_empty());
220+
assert!(async_pmts_msgs
221+
.into_iter()
222+
.all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
223+
224+
// Receiving a duplicate invoice will have no effect.
225+
nodes[1]
226+
.onion_messenger
227+
.send_onion_message(
228+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(
229+
valid_static_invoice,
230+
)),
231+
MessageSendInstructions::WithoutReplyPath {
232+
destination: Destination::BlindedPath(invreq_reply_path),
233+
},
234+
)
235+
.unwrap();
236+
let dup_static_invoice_om = nodes[1]
237+
.onion_messenger
238+
.next_onion_message_for_peer(nodes[0].node.get_our_node_id())
239+
.unwrap();
240+
nodes[0]
241+
.onion_messenger
242+
.handle_onion_message(nodes[1].node.get_our_node_id(), &dup_static_invoice_om);
243+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
244+
assert!(async_pmts_msgs.is_empty());
245+
}
246+
247+
#[test]
248+
fn pays_static_invoice() {
249+
// Test that we support the async payments flow up to and including sending the actual payment.
250+
// Async receive is not yet supported so we don't complete the payment yet.
251+
let secp_ctx = Secp256k1::new();
252+
let chanmon_cfgs = create_chanmon_cfgs(3);
253+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
254+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
255+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
256+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
257+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
258+
259+
let blinded_paths_to_always_online_node = nodes[1]
260+
.message_router
261+
.create_blinded_paths(
262+
nodes[1].node.get_our_node_id(),
263+
MessageContext::Offers(OffersContext::InvoiceRequest { nonce: Nonce([42; 16]) }),
264+
Vec::new(),
265+
&secp_ctx,
266+
)
267+
.unwrap();
268+
let (offer_builder, offer_nonce) = nodes[2]
269+
.node
270+
.create_async_receive_offer_builder(blinded_paths_to_always_online_node)
271+
.unwrap();
272+
let offer = offer_builder.build().unwrap();
273+
let amt_msat = 5000;
274+
let payment_id = PaymentId([1; 32]);
275+
let static_invoice = nodes[2]
276+
.node
277+
.create_static_invoice_builder(&offer, offer_nonce, None)
278+
.unwrap()
279+
.build_and_sign(&secp_ctx)
280+
.unwrap();
281+
282+
nodes[0]
283+
.node
284+
.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None)
285+
.unwrap();
286+
287+
// Don't forward the invreq since we don't support retrieving the static invoice from the
288+
// recipient's LSP yet, instead manually construct the response.
289+
let invreq_om = nodes[0]
290+
.onion_messenger
291+
.next_onion_message_for_peer(nodes[1].node.get_our_node_id())
292+
.unwrap();
293+
let invreq_reply_path = offers_tests::extract_invoice_request(&nodes[1], &invreq_om).1;
294+
295+
nodes[1]
296+
.onion_messenger
297+
.send_onion_message(
298+
ParsedOnionMessageContents::<Infallible>::Offers(OffersMessage::StaticInvoice(
299+
static_invoice,
300+
)),
301+
MessageSendInstructions::WithoutReplyPath {
302+
destination: Destination::BlindedPath(invreq_reply_path),
303+
},
304+
)
305+
.unwrap();
306+
let static_invoice_om = nodes[1]
307+
.onion_messenger
308+
.next_onion_message_for_peer(nodes[0].node.get_our_node_id())
309+
.unwrap();
310+
nodes[0]
311+
.onion_messenger
312+
.handle_onion_message(nodes[1].node.get_our_node_id(), &static_invoice_om);
313+
let mut async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
314+
assert!(!async_pmts_msgs.is_empty());
315+
assert!(async_pmts_msgs
316+
.iter()
317+
.all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
318+
319+
// Manually send the message and context releasing the HTLC since the recipient doesn't support
320+
// responding themselves yet.
321+
let held_htlc_avail_reply_path = match async_pmts_msgs.pop().unwrap().1 {
322+
MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => reply_path,
323+
_ => panic!(),
324+
};
325+
nodes[2]
326+
.onion_messenger
327+
.send_onion_message(
328+
ParsedOnionMessageContents::<Infallible>::AsyncPayments(
329+
AsyncPaymentsMessage::ReleaseHeldHtlc(ReleaseHeldHtlc {}),
330+
),
331+
MessageSendInstructions::WithoutReplyPath {
332+
destination: Destination::BlindedPath(held_htlc_avail_reply_path),
333+
},
334+
)
335+
.unwrap();
336+
337+
let release_held_htlc_om = nodes[2]
338+
.onion_messenger
339+
.next_onion_message_for_peer(nodes[0].node.get_our_node_id())
340+
.unwrap();
341+
nodes[0]
342+
.onion_messenger
343+
.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);
344+
345+
// Check that we've queued the HTLCs of the async keysend payment.
346+
let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
347+
assert_eq!(htlc_updates.update_add_htlcs.len(), 1);
348+
check_added_monitors!(nodes[0], 1);
349+
350+
// Receiving a duplicate release_htlc message doesn't result in duplicate payment.
351+
nodes[0]
352+
.onion_messenger
353+
.handle_onion_message(nodes[2].node.get_our_node_id(), &release_held_htlc_om);
354+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
355+
}

lightning/src/ln/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ pub use onion_utils::create_payment_onion;
5555
#[cfg(test)]
5656
#[allow(unused_mut)]
5757
mod blinded_payment_tests;
58+
#[cfg(all(test, async_payments))]
59+
#[allow(unused_mut)]
60+
mod async_payments_tests;
5861
#[cfg(test)]
5962
#[allow(unused_mut)]
6063
mod functional_tests;

lightning/src/ln/offers_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ fn extract_offer_nonce<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessa
199199
}
200200
}
201201

202-
fn extract_invoice_request<'a, 'b, 'c>(
202+
pub(super) fn extract_invoice_request<'a, 'b, 'c>(
203203
node: &Node<'a, 'b, 'c>, message: &OnionMessage
204204
) -> (InvoiceRequest, BlindedMessagePath) {
205205
match node.onion_messenger.peel_onion_message(message) {

0 commit comments

Comments
 (0)