Skip to content

Commit 692cb19

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 3fc0fcc commit 692cb19

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
@@ -73,6 +73,9 @@ mod monitor_tests;
7373
#[cfg(test)]
7474
#[allow(unused_mut)]
7575
mod shutdown_tests;
76+
#[cfg(test)]
77+
#[allow(unused_mut)]
78+
mod offers_tests;
7679

7780
pub use self::peer_channel_encryptor::LN_MAX_MSG_LEN;
7881

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
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(), alice_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)