Skip to content

Commit af23540

Browse files
valentinewallacetnull
authored andcommitted
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 eae699b commit af23540

File tree

3 files changed

+363
-1
lines changed

3 files changed

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

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)