Skip to content

Commit 86cfb96

Browse files
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 18fb0f2 commit 86cfb96

File tree

1 file changed

+211
-0
lines changed

1 file changed

+211
-0
lines changed

lightning/src/ln/blinded_payment_tests.rs

+211
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@ use crate::util::ser::WithoutLength;
3535
use crate::util::test_utils;
3636
use lightning_invoice::RawBolt11Invoice;
3737
#[cfg(async_payments)] use {
38+
crate::blinded_path::BlindedHop,
39+
crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath, OffersContext},
40+
crate::ln::channelmanager::Verification,
3841
crate::ln::inbound_payment,
42+
crate::ln::msgs::OnionMessageHandler,
43+
crate::offers::nonce::Nonce,
44+
crate::onion_message::async_payments::{AsyncPaymentsMessage, AsyncPaymentsMessageHandler, ReleaseHeldHtlc},
45+
crate::onion_message::offers::{OffersMessage, OffersMessageHandler},
46+
crate::types::features::Bolt12InvoiceFeatures,
3947
crate::types::payment::PaymentPreimage,
4048
};
4149

@@ -1416,6 +1424,209 @@ fn custom_tlvs_to_blinded_path() {
14161424
);
14171425
}
14181426

1427+
#[test]
1428+
#[cfg(async_payments)]
1429+
fn static_invoice_unknown_required_features() {
1430+
// Test that we will fail to pay a static invoice with unsupported required features.
1431+
let secp_ctx = Secp256k1::new();
1432+
let chanmon_cfgs = create_chanmon_cfgs(3);
1433+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1434+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
1435+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1436+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1437+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
1438+
1439+
// Use a dummy blinded path because we don't support retrieving the static invoice from the
1440+
// recipient's LSP yet.
1441+
let dummy_blinded_path_to_always_online_node = BlindedMessagePath::from_raw(
1442+
nodes[1].node.get_our_node_id(), test_utils::pubkey(42),
1443+
vec![BlindedHop { blinded_node_id: test_utils::pubkey(42), encrypted_payload: vec![42; 32] }]
1444+
);
1445+
let (offer_builder, nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node]).unwrap();
1446+
let offer = offer_builder.build().unwrap();
1447+
let static_invoice_unknown_req_features = nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
1448+
&offer, nonce, None
1449+
)
1450+
.unwrap()
1451+
.features_unchecked(Bolt12InvoiceFeatures::unknown())
1452+
.build_and_sign(&secp_ctx).unwrap();
1453+
1454+
let amt_msat = 5000;
1455+
let payment_id = PaymentId([1; 32]);
1456+
// Set the random bytes so we can predict the offer outbound payment context nonce.
1457+
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([42; 32]);
1458+
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();
1459+
1460+
// Don't forward the invreq since we don't support retrieving the static invoice from the
1461+
// recipient's LSP yet, instead just provide the invoice directly to the payer.
1462+
let _invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
1463+
1464+
let inbound_payment_key = inbound_payment::ExpandedKey::new(
1465+
&nodes[0].keys_manager.get_inbound_payment_key_material()
1466+
);
1467+
let offer_outbound_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
1468+
let hmac = payment_id.hmac_for_offer_payment(offer_outbound_context_nonce, &inbound_payment_key);
1469+
if nodes[0].node.handle_message(
1470+
OffersMessage::StaticInvoice(static_invoice_unknown_req_features),
1471+
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
1472+
).is_some() { panic!() }
1473+
1474+
let events = nodes[0].node.get_and_clear_pending_events();
1475+
assert_eq!(events.len(), 1);
1476+
match events[0] {
1477+
Event::PaymentFailed { payment_hash, payment_id: ev_payment_id, reason } => {
1478+
assert_eq!(payment_hash, None);
1479+
assert_eq!(payment_id, ev_payment_id);
1480+
assert_eq!(reason, Some(PaymentFailureReason::UnknownRequiredFeatures));
1481+
},
1482+
_ => panic!()
1483+
}
1484+
}
1485+
1486+
#[test]
1487+
#[cfg(async_payments)]
1488+
fn ignore_unexpected_static_invoice() {
1489+
// Test that we'll ignore unexpected static invoices, invoices that don't match our invoice
1490+
// request, and duplicate invoices.
1491+
let secp_ctx = Secp256k1::new();
1492+
let chanmon_cfgs = create_chanmon_cfgs(3);
1493+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1494+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
1495+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1496+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1497+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
1498+
1499+
// Initiate payment to the sender's intended offer.
1500+
let dummy_blinded_path_to_always_online_node = BlindedMessagePath::from_raw(
1501+
nodes[1].node.get_our_node_id(), test_utils::pubkey(42),
1502+
vec![BlindedHop { blinded_node_id: test_utils::pubkey(42), encrypted_payload: vec![42; 32] }]
1503+
);
1504+
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node.clone()]).unwrap();
1505+
let offer = offer_builder.build().unwrap();
1506+
let amt_msat = 5000;
1507+
let payment_id = PaymentId([1; 32]);
1508+
// Set the random bytes so we can predict the offer outbound payment context nonce.
1509+
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([42; 32]);
1510+
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();
1511+
1512+
// Don't forward the invreq since we don't support retrieving the static invoice from the
1513+
// recipient's LSP yet, instead just provide the invoice directly to the payer.
1514+
let _invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
1515+
1516+
// Create a static invoice with the same payment_id but corresponding to a different offer.
1517+
let unexpected_static_invoice = {
1518+
let (offer_builder, nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node]).unwrap();
1519+
let sender_unintended_offer = offer_builder.build().unwrap();
1520+
1521+
nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
1522+
&sender_unintended_offer, nonce, None
1523+
).unwrap().build_and_sign(&secp_ctx).unwrap()
1524+
};
1525+
1526+
// Check that we'll ignore the unexpected static invoice.
1527+
let inbound_payment_key = inbound_payment::ExpandedKey::new(
1528+
&nodes[0].keys_manager.get_inbound_payment_key_material()
1529+
);
1530+
let offer_outbound_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
1531+
let hmac = payment_id.hmac_for_offer_payment(offer_outbound_context_nonce, &inbound_payment_key);
1532+
assert!(nodes[0].node.handle_message(
1533+
OffersMessage::StaticInvoice(unexpected_static_invoice),
1534+
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
1535+
).is_none());
1536+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
1537+
assert!(async_pmts_msgs.is_empty());
1538+
assert!(nodes[0].node.get_and_clear_pending_events().is_empty());
1539+
1540+
// A valid static invoice corresponding to the correct offer will succeed and cause us to send a
1541+
// held_htlc_available onion message.
1542+
let valid_static_invoice = nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
1543+
&offer, offer_nonce, None
1544+
).unwrap().build_and_sign(&secp_ctx).unwrap();
1545+
1546+
assert!(nodes[0].node.handle_message(
1547+
OffersMessage::StaticInvoice(valid_static_invoice.clone()),
1548+
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
1549+
).is_none());
1550+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
1551+
assert!(!async_pmts_msgs.is_empty());
1552+
assert!(async_pmts_msgs.into_iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
1553+
1554+
// Receiving a duplicate invoice will have no effect.
1555+
assert!(nodes[0].node.handle_message(
1556+
OffersMessage::StaticInvoice(valid_static_invoice),
1557+
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
1558+
).is_none());
1559+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
1560+
assert!(async_pmts_msgs.is_empty());
1561+
}
1562+
1563+
#[test]
1564+
#[cfg(async_payments)]
1565+
fn pays_static_invoice() {
1566+
// Test that we support the async payments flow up to and including sending the actual payment.
1567+
// Async receive is not yet supported so we don't complete the payment yet.
1568+
let secp_ctx = Secp256k1::new();
1569+
let chanmon_cfgs = create_chanmon_cfgs(3);
1570+
let node_cfgs = create_node_cfgs(3, &chanmon_cfgs);
1571+
let node_chanmgrs = create_node_chanmgrs(3, &node_cfgs, &[None, None, None]);
1572+
let nodes = create_network(3, &node_cfgs, &node_chanmgrs);
1573+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 1_000_000, 0);
1574+
create_unannounced_chan_between_nodes_with_value(&nodes, 1, 2, 1_000_000, 0);
1575+
1576+
let dummy_blinded_path_to_always_online_node = BlindedMessagePath::from_raw(
1577+
nodes[1].node.get_our_node_id(), test_utils::pubkey(42),
1578+
vec![BlindedHop { blinded_node_id: test_utils::pubkey(42), encrypted_payload: vec![42; 32] }]
1579+
);
1580+
let (offer_builder, offer_nonce) = nodes[2].node.create_async_receive_offer_builder(vec![dummy_blinded_path_to_always_online_node.clone()]).unwrap();
1581+
let offer = offer_builder.build().unwrap();
1582+
let amt_msat = 5000;
1583+
let payment_id = PaymentId([1; 32]);
1584+
// Set the random bytes so we can predict the offer outbound payment context nonce.
1585+
*nodes[0].keys_manager.override_random_bytes.lock().unwrap() = Some([42; 32]);
1586+
nodes[0].node.pay_for_offer(&offer, None, Some(amt_msat), None, payment_id, Retry::Attempts(0), None).unwrap();
1587+
1588+
// Don't forward the invreq since we don't support retrieving the static invoice from the
1589+
// recipient's LSP yet, instead just provide the invoice directly to the payer.
1590+
let _invreq_om = nodes[0].onion_messenger.next_onion_message_for_peer(nodes[1].node.get_our_node_id()).unwrap();
1591+
1592+
let inbound_payment_key = inbound_payment::ExpandedKey::new(
1593+
&nodes[0].keys_manager.get_inbound_payment_key_material()
1594+
);
1595+
let offer_outbound_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
1596+
let hmac = payment_id.hmac_for_offer_payment(offer_outbound_context_nonce, &inbound_payment_key);
1597+
1598+
let static_invoice = nodes[2].node.create_static_invoice_builder_for_async_receive_offer(
1599+
&offer, offer_nonce, None
1600+
).unwrap().build_and_sign(&secp_ctx).unwrap();
1601+
1602+
assert!(nodes[0].node.handle_message(
1603+
OffersMessage::StaticInvoice(static_invoice),
1604+
Some(OffersContext::OutboundPayment { payment_id, nonce: offer_outbound_context_nonce, hmac: Some(hmac) }), None
1605+
).is_none());
1606+
let async_pmts_msgs = AsyncPaymentsMessageHandler::release_pending_messages(nodes[0].node);
1607+
assert!(!async_pmts_msgs.is_empty());
1608+
assert!(async_pmts_msgs.into_iter().all(|(msg, _)| matches!(msg, AsyncPaymentsMessage::HeldHtlcAvailable(_))));
1609+
1610+
// Manually create the message and context releasing the HTLC since the recipient doesn't support
1611+
// responding themselves yet.
1612+
let outbound_async_payment_context_nonce = Nonce::from_entropy_source(nodes[0].keys_manager);
1613+
let outbound_async_payment_context = AsyncPaymentsContext::OutboundPayment {
1614+
payment_id,
1615+
nonce: outbound_async_payment_context_nonce,
1616+
hmac: payment_id.hmac_for_async_payment(outbound_async_payment_context_nonce, &inbound_payment_key),
1617+
};
1618+
nodes[0].node.handle_release_held_htlc(ReleaseHeldHtlc {}, outbound_async_payment_context.clone());
1619+
1620+
// Check that we've queued the HTLCs of the async keysend payment.
1621+
let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
1622+
assert_eq!(htlc_updates.update_add_htlcs.len(), 1);
1623+
check_added_monitors!(nodes[0], 1);
1624+
1625+
// Receiving a duplicate release_htlc message doesn't result in duplicate payment.
1626+
nodes[0].node.handle_release_held_htlc(ReleaseHeldHtlc {}, outbound_async_payment_context.clone());
1627+
assert!(nodes[0].node.get_and_clear_pending_msg_events().is_empty());
1628+
}
1629+
14191630
fn secret_from_hex(hex: &str) -> SecretKey {
14201631
SecretKey::from_slice(&<Vec<u8>>::from_hex(hex).unwrap()).unwrap()
14211632
}

0 commit comments

Comments
 (0)