Skip to content

Commit a664a18

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 c07c1cb commit a664a18

File tree

2 files changed

+216
-0
lines changed

2 files changed

+216
-0
lines changed

lightning/src/ln/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ mod shutdown_tests;
7979
#[cfg(all(test, async_signing))]
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

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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+
fn route_bolt12_payment<'a, 'b, 'c>(
42+
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
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+
fn claim_bolt12_payment<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>]) {
61+
let recipient = &path[path.len() - 1];
62+
match get_event!(recipient, Event::PaymentClaimable) {
63+
Event::PaymentClaimable {
64+
purpose: PaymentPurpose::InvoicePayment {
65+
payment_preimage: Some(payment_preimage), ..
66+
}, ..
67+
} => claim_payment(node, path, payment_preimage),
68+
_ => panic!(),
69+
};
70+
}
71+
72+
fn extract_invoice_request<'a, 'b, 'c>(
73+
node: &Node<'a, 'b, 'c>, message: &OnionMessage
74+
) -> (InvoiceRequest, Option<BlindedPath>) {
75+
match node.onion_messenger.peel_onion_message(message) {
76+
Ok(PeeledOnion::Receive(message, _, reply_path)) => match message {
77+
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
78+
OffersMessage::InvoiceRequest(invoice_request) => (invoice_request, reply_path),
79+
OffersMessage::Invoice(invoice) => panic!("Unexpected invoice: {:?}", invoice),
80+
OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
81+
},
82+
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
83+
},
84+
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
85+
Err(e) => panic!("Failed to process onion message {:?}", e),
86+
}
87+
}
88+
89+
fn extract_invoice<'a, 'b, 'c>(node: &Node<'a, 'b, 'c>, message: &OnionMessage) -> Bolt12Invoice {
90+
match node.onion_messenger.peel_onion_message(message) {
91+
Ok(PeeledOnion::Receive(message, _, _)) => match message {
92+
ParsedOnionMessageContents::Offers(offers_message) => match offers_message {
93+
OffersMessage::InvoiceRequest(invoice_request) => panic!("Unexpected invoice_request: {:?}", invoice_request),
94+
OffersMessage::Invoice(invoice) => invoice,
95+
OffersMessage::InvoiceError(error) => panic!("Unexpected invoice_error: {:?}", error),
96+
},
97+
ParsedOnionMessageContents::Custom(message) => panic!("Unexpected custom message: {:?}", message),
98+
},
99+
Ok(PeeledOnion::Forward(_, _)) => panic!("Unexpected onion message forward"),
100+
Err(e) => panic!("Failed to process onion message {:?}", e),
101+
}
102+
}
103+
104+
/// Checks that an offer can be paid through a one-hop blinded path and that ephemeral pubkeys are
105+
/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the
106+
/// introduction node of the blinded path.
107+
#[test]
108+
fn creates_and_pays_for_offer_using_one_hop_blinded_path() {
109+
let chanmon_cfgs = create_chanmon_cfgs(2);
110+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
111+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
112+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
113+
114+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
115+
116+
let alice = &nodes[0];
117+
let alice_id = alice.node.get_our_node_id();
118+
let bob = &nodes[1];
119+
let bob_id = bob.node.get_our_node_id();
120+
121+
let offer = alice.node
122+
.create_offer_builder("coffee".to_string()).unwrap()
123+
.amount_msats(10_000_000)
124+
.build().unwrap();
125+
assert_ne!(offer.signing_pubkey(), alice_id);
126+
assert!(!offer.paths().is_empty());
127+
for path in offer.paths() {
128+
assert_eq!(path.introduction_node_id, alice_id);
129+
}
130+
131+
let payment_id = PaymentId([1; 32]);
132+
bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None).unwrap();
133+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
134+
135+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
136+
alice.onion_messenger.handle_onion_message(&bob_id, &onion_message);
137+
138+
let (invoice_request, reply_path) = extract_invoice_request(alice, &onion_message);
139+
assert_eq!(invoice_request.amount_msats(), None);
140+
assert_ne!(invoice_request.payer_id(), bob_id);
141+
assert_eq!(reply_path.unwrap().introduction_node_id, bob_id);
142+
143+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
144+
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
145+
146+
let invoice = extract_invoice(bob, &onion_message);
147+
assert_eq!(invoice.amount_msats(), 10_000_000);
148+
assert_ne!(invoice.signing_pubkey(), alice_id);
149+
assert!(!invoice.payment_paths().is_empty());
150+
for (_, path) in invoice.payment_paths() {
151+
assert_eq!(path.introduction_node_id, alice_id);
152+
}
153+
154+
route_bolt12_payment(bob, &[alice], &invoice);
155+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
156+
157+
claim_bolt12_payment(bob, &[alice]);
158+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
159+
}
160+
161+
/// Checks that a refund can be paid through a one-hop blinded path and that ephemeral pubkeys are
162+
/// used rather than exposing a node's pubkey. However, the node's pubkey is still used as the
163+
/// introduction node of the blinded path.
164+
#[test]
165+
fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
166+
let chanmon_cfgs = create_chanmon_cfgs(2);
167+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
168+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
169+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
170+
171+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
172+
173+
let alice = &nodes[0];
174+
let alice_id = alice.node.get_our_node_id();
175+
let bob = &nodes[1];
176+
let bob_id = bob.node.get_our_node_id();
177+
178+
let absolute_expiry = Duration::from_secs(u64::MAX);
179+
let payment_id = PaymentId([1; 32]);
180+
let refund = bob.node
181+
.create_refund_builder(
182+
"refund".to_string(), 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None
183+
)
184+
.unwrap()
185+
.build().unwrap();
186+
assert_eq!(refund.amount_msats(), 10_000_000);
187+
assert_eq!(refund.absolute_expiry(), Some(absolute_expiry));
188+
assert_ne!(refund.payer_id(), bob_id);
189+
assert!(!refund.paths().is_empty());
190+
for path in refund.paths() {
191+
assert_eq!(path.introduction_node_id, bob_id);
192+
}
193+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
194+
195+
alice.node.request_refund_payment(&refund).unwrap();
196+
197+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
198+
bob.onion_messenger.handle_onion_message(&alice_id, &onion_message);
199+
200+
let invoice = extract_invoice(bob, &onion_message);
201+
assert_eq!(invoice.amount_msats(), 10_000_000);
202+
assert_ne!(invoice.signing_pubkey(), alice_id);
203+
assert!(!invoice.payment_paths().is_empty());
204+
for (_, path) in invoice.payment_paths() {
205+
assert_eq!(path.introduction_node_id, alice_id);
206+
}
207+
208+
route_bolt12_payment(bob, &[alice], &invoice);
209+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
210+
211+
claim_bolt12_payment(bob, &[alice]);
212+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
213+
}

0 commit comments

Comments
 (0)