Skip to content

Commit bd2a110

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 769a4db commit bd2a110

2 files changed

Lines changed: 175 additions & 1 deletion

File tree

lightning/src/ln/offers_tests.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use crate::offers::invoice::Bolt12Invoice;
6363
use crate::offers::invoice_error::InvoiceError;
6464
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestVerifiedFromOffer};
6565
use crate::offers::nonce::Nonce;
66-
use crate::offers::offer::OfferBuilder;
66+
use crate::offers::offer::{Amount, CurrencyCode, OfferBuilder};
6767
use crate::offers::parse::Bolt12SemanticError;
6868
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, MessageSendInstructions, NodeIdMessageRouter, NullMessageRouter, PeeledOnion, DUMMY_HOPS_PATH_LENGTH, QR_CODED_DUMMY_HOPS_PATH_LENGTH};
6969
use crate::onion_message::offers::OffersMessage;
@@ -1554,6 +1554,64 @@ fn pays_bolt12_invoice_asynchronously() {
15541554
);
15551555
}
15561556

1557+
#[test]
1558+
fn creates_and_pays_bolt12_invoice_for_currency_denominated_offer() {
1559+
let chanmon_cfgs = create_chanmon_cfgs(2);
1560+
let node_cfgs = create_node_cfgs(2, &chanmon_cfgs);
1561+
let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]);
1562+
let nodes = create_network(2, &node_cfgs, &node_chanmgrs);
1563+
1564+
create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000);
1565+
1566+
let alice = &nodes[0];
1567+
let alice_id = alice.node.get_our_node_id();
1568+
let bob = &nodes[1];
1569+
let bob_id = bob.node.get_our_node_id();
1570+
1571+
let offer = alice.node
1572+
.create_offer_builder()
1573+
.unwrap()
1574+
.amount(Amount::Currency {
1575+
iso4217_code: CurrencyCode::new(*b"USD").unwrap(),
1576+
amount: 10,
1577+
})
1578+
.build()
1579+
.unwrap();
1580+
1581+
let payment_id = PaymentId([1; 32]);
1582+
bob.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap();
1583+
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);
1584+
1585+
let onion_message = bob.onion_messenger.next_onion_message_for_peer(alice_id).unwrap();
1586+
let (invoice_request, _) = extract_invoice_request(alice, &onion_message);
1587+
assert_eq!(invoice_request.amount_msats(), None);
1588+
1589+
let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
1590+
offer_id: offer.id(),
1591+
invoice_request: InvoiceRequestFields {
1592+
payer_signing_pubkey: invoice_request.payer_signing_pubkey(),
1593+
quantity: None,
1594+
payer_note_truncated: None,
1595+
human_readable_name: None,
1596+
},
1597+
payment_metadata: None,
1598+
});
1599+
1600+
alice.onion_messenger.handle_onion_message(bob_id, &onion_message);
1601+
1602+
let invoice_onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
1603+
let (invoice, _) = extract_invoice(bob, &invoice_onion_message);
1604+
assert_eq!(invoice.amount_msats(), 10_000);
1605+
1606+
bob.onion_messenger.handle_onion_message(alice_id, &invoice_onion_message);
1607+
1608+
route_bolt12_payment(bob, &[alice], &invoice);
1609+
expect_recent_payment!(bob, RecentPaymentDetails::Pending, payment_id);
1610+
1611+
claim_bolt12_payment(bob, &[alice], payment_context, &invoice);
1612+
expect_recent_payment!(bob, RecentPaymentDetails::Fulfilled, payment_id);
1613+
}
1614+
15571615
/// Checks that an offer can be created using an unannounced node as a blinded path's introduction
15581616
/// node. This is only preferred if there are no other options which may indicated either the offer
15591617
/// 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
@@ -1917,6 +1917,7 @@ mod tests {
19171917
use crate::types::features::{Bolt12InvoiceFeatures, InvoiceRequestFeatures, OfferFeatures};
19181918
use crate::types::string::PrintableString;
19191919
use crate::util::ser::{BigSize, Iterable, Writeable};
1920+
use crate::util::test_utils::TestCurrencyConversion;
19201921
#[cfg(not(c_bindings))]
19211922
use {crate::offers::offer::OfferBuilder, crate::offers::refund::RefundBuilder};
19221923
#[cfg(c_bindings)]
@@ -2557,6 +2558,121 @@ mod tests {
25572558
}
25582559
}
25592560

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

0 commit comments

Comments
 (0)