Skip to content

Commit cacfa05

Browse files
committed
Functional tests for BOLT 12 Offers payment flow
ChannelManager provides utilities to create offers and refunds along with utilities to initiate and request payment for them, respectively. It also manages the payment flow via implementing OffersMessageHandler. Test that functionality, including the resulting event generation.
1 parent d0006a8 commit cacfa05

File tree

2 files changed

+222
-0
lines changed

2 files changed

+222
-0
lines changed

lightning/src/ln/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ mod shutdown_tests;
7979
#[cfg(test)]
8080
#[allow(unused_mut)]
8181
mod async_signer_tests;
82+
#[cfg(test)]
83+
#[allow(unused_mut)]
84+
mod offers_tests;
8285

8386
pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;
8487

lightning/src/ln/offers_tests.rs

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
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+
//! Functional tests for the BOLT 12 Offers payment flow.
11+
//!
12+
//! [`ChannelManager`] provides utilities to create [`Offer`]s and [`Refund`]s along with utilities
13+
//! to initiate and request payment for them, respectively. It also manages the payment flow via
14+
//! implementing [`OffersMessageHandler`]. This module tests that functionality, including the
15+
//! resulting [`Event`] generation.
16+
17+
use core::time::Duration;
18+
use crate::blinded_path::BlindedPath;
19+
use crate::events::{Event, MessageSendEventsProvider, PaymentPurpose};
20+
use crate::ln::channelmanager::{PaymentId, RecentPaymentDetails, Retry};
21+
use crate::ln::functional_test_utils::*;
22+
use crate::ln::msgs::{OnionMessage, OnionMessageHandler};
23+
use crate::offers::invoice::Bolt12Invoice;
24+
use crate::offers::invoice_request::InvoiceRequest;
25+
use crate::onion_message::{OffersMessage, ParsedOnionMessageContents, PeeledOnion};
26+
27+
use crate::prelude::*;
28+
29+
macro_rules! expect_recent_payment {
30+
($node: expr, $payment_state: path, $payment_id: expr) => {
31+
match $node.node.list_recent_payments().first() {
32+
Some(&$payment_state { payment_id: actual_payment_id, .. }) => {
33+
assert_eq!($payment_id, actual_payment_id);
34+
},
35+
Some(_) => panic!("Unexpected recent payment state"),
36+
None => panic!("No recent payments"),
37+
}
38+
}
39+
}
40+
41+
macro_rules! route_payment {
42+
($node: expr, $path: expr, $invoice: expr) => {
43+
{
44+
// Monitor added when handling the invoice onion message.
45+
check_added_monitors($node, 1);
46+
47+
let mut events = $node.node.get_and_clear_pending_msg_events();
48+
assert_eq!(events.len(), 1);
49+
let ev = remove_first_msg_event_to_node(&$path[0].node.get_our_node_id(), &mut events);
50+
51+
// Use a fake payment_hash and bypass checking for the PaymentClaimable event since the
52+
// invoice contains the payment_hash but it was encrypted inside an onion message.
53+
let amount_msats = $invoice.amount_msats();
54+
let payment_hash = $invoice.payment_hash();
55+
do_pass_along_path(
56+
$node, $path, amount_msats, payment_hash, None, ev, false, false, None, false
57+
);
58+
}
59+
}
60+
}
61+
62+
macro_rules! claim_payment {
63+
($node: expr, $path: expr) => {
64+
{
65+
let recipient = &$path[$path.len() - 1];
66+
match get_event!(recipient, Event::PaymentClaimable) {
67+
Event::PaymentClaimable {
68+
purpose: PaymentPurpose::InvoicePayment {
69+
payment_preimage: Some(payment_preimage), ..
70+
}, ..
71+
} => claim_payment($node, $path, payment_preimage),
72+
_ => panic!(),
73+
};
74+
}
75+
}
76+
}
77+
78+
fn extract_invoice_request<'a, 'b, 'c>(
79+
node: &Node<'a, 'b, 'c>, message: &OnionMessage
80+
) -> (InvoiceRequest, Option<BlindedPath>) {
81+
match node.onion_messenger.peel_onion_message(message) {
82+
Ok(PeeledOnion::Receive(message, _, reply_path)) => match message {
83+
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
84+
OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path),
85+
OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice),
86+
OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
87+
},
88+
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
89+
},
90+
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
91+
Err(e) => panic!("Failed to process onion message {:?}", e),
92+
}
93+
}
94+
95+
fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Bolt12Invoice {
96+
match node.onion_messenger.peel_onion_message(message) {
97+
Ok(PeeledOnion::Receive(message, _, _)) => match message {
98+
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
99+
OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request),
100+
OffersMessage::Invoice(invoice) => invoice,
101+
OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
102+
},
103+
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
104+
},
105+
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
106+
Err(e) => panic!("Failed to process onion message {:?}", e),
107+
}
108+
}
109+
110+
/// Checks that an offer can be paid through blinded paths and that ephemeral pubkeys are used
111+
/// rather than exposing a node's pubkey. Since a direct connection is currently required, the
112+
/// node's pubkey is instead used as the introduction node in any blinded paths.
113+
#[test]
114+
fn creates_and_pays_for_offer_using_blinded_paths() {
115+
let chanmon_cfgs = create_chanmon_cfgs(2);
116+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
117+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
118+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
119+
120+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
121+
122+
let alice = &nodes[0];
123+
let alice_id = alice.node.get_our_node_id();
124+
let bob = &nodes[1];
125+
let bob_id = bob.node.get_our_node_id();
126+
127+
let offer = alice.node
128+
.create_offer_builder("coffee".to_string())
129+
.amount_msats(10_000_000)
130+
.build().unwrap();
131+
assert_ne!(offer.signing_pubkey(), alice_id);
132+
assert!(!offer.paths().is_empty());
133+
for path in offer.paths() {
134+
assert_eq!(path.introduction_node_id, alice_id);
135+
}
136+
137+
let payment_id = PaymentId([1; 32]);
138+
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
139+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
140+
141+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
142+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
143+
144+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
145+
assert_eq!(invoice_request.amount_msats(), None);
146+
assert_ne!(invoice_request.payer_id(), bob_id);
147+
assert_eq!(reply_path.unwrap().introduction_node_id, bob_id);
148+
149+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
150+
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
151+
152+
let invoice = extract_invoice(bob, &onion_message);
153+
assert_eq!(invoice.amount_msats(), 10_000_000);
154+
assert_ne!(invoice.signing_pubkey(), alice_id);
155+
assert!(!invoice.payment_paths().is_empty());
156+
for (_, path) in invoice.payment_paths() {
157+
assert_eq!(path.introduction_node_id, alice_id);
158+
}
159+
160+
route_payment!(bob, &[alice], invoice);
161+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
162+
163+
claim_payment!(bob, &[alice]);
164+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
165+
}
166+
167+
/// Checks that a refund can be paid through blinded paths and that ephemeral pubkeys are used
168+
/// rather than exposing a node's pubkey. Since a direct connection is currently required, the
169+
/// node's pubkey is instead used as the introduction node in any blinded paths.
170+
#[test]
171+
fn creates_and_pays_for_refund_using_blinded_paths() {
172+
let chanmon_cfgs = create_chanmon_cfgs(2);
173+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
174+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
175+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
176+
177+
create_unannounced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
178+
179+
let alice = &nodes[0];
180+
let alice_id = alice.node.get_our_node_id();
181+
let bob = &nodes[1];
182+
let bob_id = bob.node.get_our_node_id();
183+
184+
let absolute_expiry = Duration::from_secs(u64::MAX);
185+
let payment_id = PaymentId([1; 32]);
186+
let refund = bob.node
187+
.create_refund_builder(
188+
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
189+
)
190+
.unwrap()
191+
.build().unwrap();
192+
assert_eq!(refund.amount_msats(), 10_000_000);
193+
assert_eq!(refund.absolute_expiry(), Some(absolute_expiry));
194+
assert_ne!(refund.payer_id(), bob_id);
195+
assert!(!refund.paths().is_empty());
196+
for path in refund.paths() {
197+
assert_eq!(path.introduction_node_id, bob_id);
198+
}
199+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
200+
201+
alice.node.request_refund_payment(&refund).unwrap();
202+
203+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
204+
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
205+
206+
let invoice = extract_invoice(bob, &onion_message);
207+
assert_eq!(invoice.amount_msats(), 10_000_000);
208+
assert_ne!(invoice.signing_pubkey(), alice_id);
209+
assert!(!invoice.payment_paths().is_empty());
210+
for (_, path) in invoice.payment_paths() {
211+
assert_eq!(path.introduction_node_id, alice_id);
212+
}
213+
214+
route_payment!(bob, &[alice], invoice);
215+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
216+
217+
claim_payment!(bob, &[alice]);
218+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
219+
}

0 commit comments

Comments
 (0)