Skip to content

Commit 45fdf6c

Browse files
shaavancodex
andcommitted
[test] Cover currency-denominated invoice verification paths
Add end-to-end and payer-side tests for currency-denominated offers and invoices. This consolidates coverage for the standard payment flow, excessive invoice rejection, unverifiable fiat invoices when conversion support is unavailable, and quantity-scaled invoice requests. The combined coverage exercises the main invoice amount verification paths introduced by currency-denominated offer support. AI-assisted: Planning and writing the tests Co-Authored-By: OpenAI Codex <codex@openai.com>
1 parent 325c6dd commit 45fdf6c

2 files changed

Lines changed: 174 additions & 0 deletions

File tree

lightning/src/ln/offers_tests.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ use crate::offers::invoice::Bolt12Invoice;
6161
use crate::offers::invoice_error::InvoiceError;
6262
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestVerifiedFromOffer};
6363
use crate::offers::nonce::Nonce;
64+
use crate::offers::offer::{Amount, CurrencyCode};
6465
use crate::offers::parse::Bolt12SemanticError;
6566
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, NodeIdMessageRouter, NullMessageRouter, PeeledOnion, DUMMY_HOPS_PATH_LENGTH, QR_CODED_DUMMY_HOPS_PATH_LENGTH};
6667
use crate::onion_message::offers::OffersMessage;
@@ -1394,6 +1395,63 @@ fn pays_bolt12_invoice_asynchronously() {
13941395
);
13951396
}
13961397

1398+
#[test]
1399+
fn creates_and_pays_bolt12_invoice_for_currency_denominated_offer() {
1400+
let chanmon_cfgs = create_chanmon_cfgs(2);
1401+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1402+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1403+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1404+
1405+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1406+
1407+
let alice = &nodes[0];
1408+
let alice_id = alice.node.get_our_node_id();
1409+
let bob = &nodes[1];
1410+
let bob_id = bob.node.get_our_node_id();
1411+
1412+
let offer = alice.node
1413+
.create_offer_builder()
1414+
.unwrap()
1415+
.amount(Amount::Currency {
1416+
iso4217_code: CurrencyCode::new(*b"USD").unwrap(),
1417+
amount: 10,
1418+
})
1419+
.build()
1420+
.unwrap();
1421+
1422+
let payment_id = PaymentId([1; 32]);
1423+
bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap();
1424+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
1425+
1426+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1427+
let (invoice_request, _) = extract_invoice_request(alice, &onion_message);
1428+
assert_eq!(invoice_request.amount_msats(), None);
1429+
1430+
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
1431+
offer_id: offer.id(),
1432+
invoice_request: InvoiceRequestFields {
1433+
payer_signing_pubkey: invoice_request.payer_signing_pubkey(),
1434+
quantity: None,
1435+
payer_note_truncated: None,
1436+
human_readable_name: None,
1437+
},
1438+
});
1439+
1440+
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
1441+
1442+
let invoice_onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1443+
let (invoice, _) = extract_invoice(bob, &invoice_onion_message);
1444+
assert_eq!(invoice.amount_msats(), 10_000);
1445+
1446+
bob.onion_messenger.handle_onion_message(alice_id, &invoice_onion_message);
1447+
1448+
route_bolt12_payment(bob, &[alice], &invoice);
1449+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
1450+
1451+
claim_bolt12_payment(bob, &[alice], payment_context, &invoice);
1452+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
1453+
}
1454+
13971455
/// Checks that an offer can be created using an unannounced node as a blinded path's introduction
13981456
/// node. This is only preferred if there are no other options which may indicated either the offer
13991457
/// is intended for the unannounced node or that the node is actually announced (e.g., an LSP) but

lightning/src/offers/invoice.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,7 @@ mod tests {
19161916
use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
19171917
use crate::types::string::PrintableString;
19181918
use crate::util::ser::{BigSize, Iterable, Writeable};
1919+
use crate::util::test_utils::TestCurrencyConversion;
19191920
#[cfg(not(c_bindings))]
19201921
use {crate::offers::offer::OfferBuilder, crate::offers::refund::RefundBuilder};
19211922
#[cfg(c_bindings)]
@@ -2556,6 +2557,121 @@ mod tests {
25562557
}
25572558
}
25582559

2560+
#[test]
2561+
fn rejects_excessive_amount_for_currency_offer() {
2562+
let expanded_key = ExpandedKey::new([42; 32]);
2563+
let entropy = FixedEntropy {};
2564+
let nonce = Nonce::from_entropy_source(&entropy);
2565+
let secp_ctx = Secp256k1::new();
2566+
let payment_id = PaymentId([1; 32]);
2567+
let converter = TestCurrencyConversion {};
2568+
2569+
let invoice = OfferBuilder::new(recipient_pubkey(), &converter)
2570+
.amount(Amount::Currency {
2571+
iso4217_code: crate::offers::offer::CurrencyCode::new(*b"USD").unwrap(),
2572+
amount: 10,
2573+
})
2574+
.build()
2575+
.unwrap()
2576+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
2577+
.unwrap()
2578+
.build_and_sign()
2579+
.unwrap()
2580+
.respond_with_no_std(&converter, payment_paths(), payment_hash(), now())
2581+
.unwrap()
2582+
.amount_msats_unchecked(10_001)
2583+
.build()
2584+
.unwrap()
2585+
.sign(recipient_sign)
2586+
.unwrap();
2587+
2588+
assert_eq!(
2589+
invoice.verify_amount_acceptable_for_payment(&converter),
2590+
Err(Bolt12SemanticError::ExcessiveAmount),
2591+
);
2592+
}
2593+
2594+
#[test]
2595+
fn fails_verifying_currency_offer_invoice_without_conversion_support() {
2596+
let expanded_key = ExpandedKey::new([42; 32]);
2597+
let entropy = FixedEntropy {};
2598+
let nonce = Nonce::from_entropy_source(&entropy);
2599+
let secp_ctx = Secp256k1::new();
2600+
let payment_id = PaymentId([1; 32]);
2601+
let converter = TestCurrencyConversion {};
2602+
2603+
let invoice = OfferBuilder::new(recipient_pubkey(), &converter)
2604+
.amount(Amount::Currency {
2605+
iso4217_code: crate::offers::offer::CurrencyCode::new(*b"USD").unwrap(),
2606+
amount: 10,
2607+
})
2608+
.build()
2609+
.unwrap()
2610+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
2611+
.unwrap()
2612+
.build_and_sign()
2613+
.unwrap()
2614+
.respond_with_no_std(&converter, payment_paths(), payment_hash(), now())
2615+
.unwrap()
2616+
.build()
2617+
.unwrap()
2618+
.sign(recipient_sign)
2619+
.unwrap();
2620+
2621+
assert_eq!(
2622+
invoice.verify_amount_acceptable_for_payment(&NullCurrencyConversion),
2623+
Err(Bolt12SemanticError::UnsupportedCurrency),
2624+
);
2625+
}
2626+
2627+
#[test]
2628+
fn verifies_currency_offer_invoice_amount_with_quantity() {
2629+
let expanded_key = ExpandedKey::new([42; 32]);
2630+
let entropy = FixedEntropy {};
2631+
let nonce = Nonce::from_entropy_source(&entropy);
2632+
let secp_ctx = Secp256k1::new();
2633+
let payment_id = PaymentId([1; 32]);
2634+
let converter = TestCurrencyConversion {};
2635+
2636+
let invoice_request = OfferBuilder::new(recipient_pubkey(), &converter)
2637+
.amount(Amount::Currency {
2638+
iso4217_code: crate::offers::offer::CurrencyCode::new(*b"USD").unwrap(),
2639+
amount: 10,
2640+
})
2641+
.supported_quantity(Quantity::Unbounded)
2642+
.build()
2643+
.unwrap()
2644+
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id)
2645+
.unwrap()
2646+
.quantity(2)
2647+
.unwrap()
2648+
.build_and_sign()
2649+
.unwrap();
2650+
2651+
let invoice = invoice_request
2652+
.respond_with_no_std(&converter, payment_paths(), payment_hash(), now())
2653+
.unwrap()
2654+
.build()
2655+
.unwrap()
2656+
.sign(recipient_sign)
2657+
.unwrap();
2658+
assert_eq!(invoice.amount_msats(), 20_000);
2659+
assert_eq!(invoice.verify_amount_acceptable_for_payment(&converter), Ok(()));
2660+
2661+
let invoice = invoice_request
2662+
.respond_with_no_std(&converter, payment_paths(), payment_hash(), now())
2663+
.unwrap()
2664+
.amount_msats_unchecked(20_001)
2665+
.build()
2666+
.unwrap()
2667+
.sign(recipient_sign)
2668+
.unwrap();
2669+
assert_eq!(
2670+
invoice.verify_amount_acceptable_for_payment(&converter),
2671+
Err(Bolt12SemanticError::ExcessiveAmount),
2672+
);
2673+
}
2674+
25592675
#[test]
25602676
fn builds_invoice_with_fallback_address() {
25612677
let expanded_key = ExpandedKey::new([42; 32]);

0 commit comments

Comments
 (0)