Skip to content

Commit 26d7eee

Browse files
committed
Introduce Bolt12 custom TLVs test
Adds an end-to-end test validating that custom TLVs propagate correctly through the Bolt12 payer flow.
1 parent 3adb9cc commit 26d7eee

File tree

2 files changed

+154
-2
lines changed

2 files changed

+154
-2
lines changed

lightning-dns-resolver/src/lib.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,14 @@ mod test {
391391
#[allow(deprecated)]
392392
nodes[0]
393393
.node
394-
.pay_for_offer_from_human_readable_name(name, amt, payment_id, custom_tlvs, opts, resolvers)
394+
.pay_for_offer_from_human_readable_name(
395+
name,
396+
amt,
397+
payment_id,
398+
custom_tlvs,
399+
opts,
400+
resolvers,
401+
)
395402
.unwrap();
396403

397404
let query = nodes[0].onion_messenger.next_onion_message_for_peer(resolver_id).unwrap();
@@ -519,5 +526,21 @@ mod test {
519526
resolvers.clone(),
520527
)
521528
.await;
529+
530+
// Pay offer with custom_tlvs
531+
pay_offer_flow(
532+
&nodes,
533+
&resolver_messenger,
534+
resolver_id,
535+
payer_id,
536+
payee_id,
537+
bs_offer,
538+
name,
539+
PaymentId([11; 32]),
540+
None,
541+
vec![(65537, vec![42; 42])],
542+
resolvers,
543+
)
544+
.await;
522545
}
523546
}

lightning/src/ln/offers_tests.rs

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,12 @@ fn check_compact_path_introduction_node<'a, 'b, 'c>(
165165

166166
fn route_bolt12_payment<'a, 'b, 'c>(
167167
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice
168+
) {
169+
route_bolt12_payment_with_custom_tlvs(node, path, invoice, Vec::new());
170+
}
171+
172+
fn route_bolt12_payment_with_custom_tlvs<'a, 'b, 'c>(
173+
node: &Node<'a, 'b, 'c>, path: &[&Node<'a, 'b, 'c>], invoice: &Bolt12Invoice, custom_tlvs: Vec<(u64, Vec<u8>)>
168174
) {
169175
// Monitor added when handling the invoice onion message.
170176
check_added_monitors(node, 1);
@@ -178,7 +184,8 @@ fn route_bolt12_payment<'a, 'b, 'c>(
178184
let amount_msats = invoice.amount_msats();
179185
let payment_hash = invoice.payment_hash();
180186
let args = PassAlongPathArgs::new(node, path, amount_msats, payment_hash, ev)
181-
.without_clearing_recipient_events();
187+
.without_clearing_recipient_events()
188+
.with_custom_tlvs(custom_tlvs);
182189
do_pass_along_path(args);
183190
}
184191

@@ -1348,6 +1355,128 @@ fn pays_bolt12_invoice_asynchronously() {
13481355
);
13491356
}
13501357

1358+
/// Checks that a deferred invoice can be paid asynchronously from an Event::InvoiceReceived.
1359+
#[test]
1360+
fn pays_bolt12_invoice_asynchronously_with_custom_tlvs() {
1361+
let mut manually_pay_cfg = test_default_channel_config();
1362+
manually_pay_cfg.manually_handle_bolt12_invoices = true;
1363+
1364+
let chanmon_cfgs = create_chanmon_cfgs(2);
1365+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1366+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, Some(manually_pay_cfg)]);
1367+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1368+
1369+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1370+
1371+
let alice = &nodes[0];
1372+
let bob = &nodes[1];
1373+
let alice_id = alice.node.get_our_node_id();
1374+
let bob_id = bob.node.get_our_node_id();
1375+
1376+
let offer = alice.node
1377+
.create_offer_builder().unwrap()
1378+
.amount_msats(10_000_000)
1379+
.build().unwrap();
1380+
1381+
const CUSTOM_TLV_TYPE: u64 = 65537;
1382+
let custom_tlvs = vec![(CUSTOM_TLV_TYPE, vec![42; 42])];
1383+
let payment_id = PaymentId([1; 32]);
1384+
1385+
bob.node
1386+
.pay_for_offer(&offer, None, payment_id, custom_tlvs.clone(), Default::default())
1387+
.unwrap();
1388+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
1389+
1390+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1391+
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
1392+
1393+
let (invoice_request, _) = extract_invoice_request(alice, &onion_message);
1394+
let expected_payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
1395+
offer_id: offer.id(),
1396+
invoice_request: InvoiceRequestFields {
1397+
payer_signing_pubkey: invoice_request.payer_signing_pubkey(),
1398+
quantity: None,
1399+
payer_note_truncated: None,
1400+
human_readable_name: None,
1401+
},
1402+
});
1403+
1404+
let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1405+
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
1406+
1407+
// Re-process the same onion message to ensure idempotency —
1408+
// we should not generate a duplicate `InvoiceReceived` event.
1409+
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
1410+
1411+
let mut events = bob.node.get_and_clear_pending_events();
1412+
assert_eq!(events.len(), 1);
1413+
1414+
let (invoice, context) = match events.pop().unwrap() {
1415+
Event::InvoiceReceived { payment_id: actual, invoice, context, .. } => {
1416+
assert_eq!(actual, payment_id);
1417+
(invoice, context)
1418+
},
1419+
_ => panic!("No Event::InvoiceReceived"),
1420+
};
1421+
1422+
assert_eq!(invoice.amount_msats(), 10_000_000);
1423+
assert_ne!(invoice.signing_pubkey(), alice_id);
1424+
assert!(!invoice.payment_paths().is_empty());
1425+
for path in invoice.payment_paths() {
1426+
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(alice_id));
1427+
}
1428+
1429+
assert!(bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()).is_ok());
1430+
assert_eq!(
1431+
bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()),
1432+
Err(Bolt12PaymentError::DuplicateInvoice),
1433+
);
1434+
1435+
route_bolt12_payment_with_custom_tlvs(bob, &[alice], &invoice, custom_tlvs.clone());
1436+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
1437+
1438+
let purpose = match get_event!(&alice, Event::PaymentClaimable) {
1439+
Event::PaymentClaimable { purpose, .. } => purpose,
1440+
_ => panic!("No Event::PaymentClaimable"),
1441+
};
1442+
let payment_preimage = purpose.preimage().expect("No preimage in Event::PaymentClaimable");
1443+
1444+
match purpose {
1445+
PaymentPurpose::Bolt12OfferPayment { payment_context, .. } => {
1446+
assert_eq!(PaymentContext::Bolt12Offer(payment_context), expected_payment_context);
1447+
},
1448+
_ => panic!("Unexpected payment purpose: {:?}", purpose),
1449+
}
1450+
1451+
let route = &[&[alice] as &[&Node]];
1452+
1453+
let claim_payment_args =
1454+
ClaimAlongRouteArgs::new(bob, route, payment_preimage)
1455+
.with_custom_tlvs(custom_tlvs);
1456+
1457+
if let Some(inv) = claim_payment_along_route(claim_payment_args).0 {
1458+
assert_eq!(inv, PaidBolt12Invoice::Bolt12Invoice(invoice.clone()));
1459+
} else {
1460+
panic!("Expected PaidBolt12Invoice::Bolt12Invoice");
1461+
}
1462+
1463+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
1464+
1465+
assert_eq!(
1466+
bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()),
1467+
Err(Bolt12PaymentError::DuplicateInvoice),
1468+
);
1469+
1470+
for _ in 0..=IDEMPOTENCY_TIMEOUT_TICKS {
1471+
bob.node.timer_tick_occurred();
1472+
}
1473+
1474+
assert_eq!(
1475+
bob.node.send_payment_for_bolt12_invoice(&invoice, context.as_ref()),
1476+
Err(Bolt12PaymentError::UnexpectedInvoice),
1477+
);
1478+
}
1479+
13511480
/// Checks that an offer can be created using an unannounced node as a blinded path's introduction
13521481
/// node. This is only preferred if there are no other options which may indicated either the offer
13531482
/// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but

0 commit comments

Comments
 (0)