Skip to content

Commit 7f1c797

Browse files
committed
Support currency-denominated Offers in InvoiceBuilder
Adds currency conversion support when responding to an `InvoiceRequest` and constructing the `InvoiceBuilder`. When the underlying Offer specifies its amount in a currency denomination, the `CurrencyConversion` implementation is used to resolve the payable amount into millisatoshis and ensure the invoice amount satisfies the Offer's requirements. This reintroduces the currency validation intentionally skipped during `InvoiceRequest` parsing, keeping parsing focused on structural validation while enforcing amount correctness at the time the Invoice is constructed.
1 parent 36a8746 commit 7f1c797

6 files changed

Lines changed: 154 additions & 110 deletions

File tree

lightning/src/ln/channelmanager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5715,6 +5715,7 @@ impl<
57155715
let features = self.bolt12_invoice_features();
57165716
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
57175717
invoice,
5718+
&self.flow.currency_conversion,
57185719
payment_id,
57195720
features,
57205721
best_block_height,

lightning/src/ln/offers_tests.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,7 @@ fn fails_paying_invoice_with_unknown_required_features() {
23672367
.build().unwrap();
23682368

23692369
let payment_id = PaymentId([1; 32]);
2370+
let conversion = TestCurrencyConversion;
23702371
david.node.pay_for_offer(&offer, None, payment_id, Default::default()).unwrap();
23712372

23722373
connect_peers(david, bob);
@@ -2401,7 +2402,7 @@ fn fails_paying_invoice_with_unknown_required_features() {
24012402

24022403
let invoice = match verified_invoice_request {
24032404
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
2404-
request.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
2405+
request.respond_using_derived_keys_no_std(&conversion, payment_paths, payment_hash, created_at).unwrap()
24052406
.features_unchecked(Bolt12InvoiceFeatures::unknown())
24062407
.build_and_sign(&secp_ctx).unwrap()
24072408
},

lightning/src/ln/outbound_payment.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use crate::ln::channelmanager::{
2424
use crate::ln::msgs::DecodeError;
2525
use crate::ln::onion_utils;
2626
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
27+
use crate::offers::currency::CurrencyConversion;
2728
use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder};
2829
use crate::offers::invoice_request::InvoiceRequest;
2930
use crate::offers::nonce::Nonce;
@@ -1230,9 +1231,10 @@ impl OutboundPayments {
12301231
Ok(())
12311232
}
12321233

1233-
pub(super) fn static_invoice_received<ES: EntropySource>(
1234-
&self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures,
1235-
best_block_height: u32, duration_since_epoch: Duration, entropy_source: ES,
1234+
pub(super) fn static_invoice_received<'a, ES: EntropySource, CC: CurrencyConversion>(
1235+
&'a self, invoice: &StaticInvoice, currency_conversion: &'a CC, payment_id: PaymentId,
1236+
features: Bolt12InvoiceFeatures, best_block_height: u32, duration_since_epoch: Duration,
1237+
entropy_source: ES,
12361238
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
12371239
) -> Result<(), Bolt12PaymentError> {
12381240
macro_rules! abandon_with_entry {
@@ -1280,6 +1282,7 @@ impl OutboundPayments {
12801282

12811283
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
12821284
invreq,
1285+
currency_conversion,
12831286
) {
12841287
Ok(amt) => amt,
12851288
Err(_) => {
@@ -3311,7 +3314,7 @@ mod tests {
33113314
.build().unwrap()
33123315
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id, &conversion).unwrap()
33133316
.build_and_sign().unwrap()
3314-
.respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap()
3317+
.respond_with_no_std(&conversion, payment_paths(), payment_hash(), created_at).unwrap()
33153318
.build().unwrap()
33163319
.sign(recipient_sign).unwrap();
33173320

@@ -3361,7 +3364,7 @@ mod tests {
33613364
.build().unwrap()
33623365
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id, &conversion).unwrap()
33633366
.build_and_sign().unwrap()
3364-
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
3367+
.respond_with_no_std(&conversion, payment_paths(), payment_hash(), now()).unwrap()
33653368
.build().unwrap()
33663369
.sign(recipient_sign).unwrap();
33673370

@@ -3427,7 +3430,7 @@ mod tests {
34273430
.build().unwrap()
34283431
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id, &conversion).unwrap()
34293432
.build_and_sign().unwrap()
3430-
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
3433+
.respond_with_no_std(&conversion, payment_paths(), payment_hash(), now()).unwrap()
34313434
.build().unwrap()
34323435
.sign(recipient_sign).unwrap();
34333436

lightning/src/offers/flow.rs

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use crate::offers::invoice_request::{
4343
InvoiceRequest, InvoiceRequestBuilder, InvoiceRequestVerifiedFromOffer, VerifiedInvoiceRequest,
4444
};
4545
use crate::offers::nonce::Nonce;
46-
use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder};
46+
use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
4747
use crate::offers::parse::Bolt12SemanticError;
4848
use crate::offers::refund::{Refund, RefundBuilder};
4949
use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder};
@@ -866,10 +866,7 @@ impl<MR: MessageRouter, CC: CurrencyConversion, L: Logger> OffersMessageFlow<MR,
866866
let payment_context =
867867
PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext { offer_nonce });
868868

869-
let amount_msat = offer.amount().and_then(|amount| match amount {
870-
Amount::Bitcoin { amount_msats } => Some(amount_msats),
871-
Amount::Currency { .. } => None,
872-
});
869+
let amount_msat = offer.resolve_offer_amount(&self.currency_conversion)?;
873870

874871
let created_at = self.duration_since_epoch();
875872

@@ -998,9 +995,12 @@ impl<MR: MessageRouter, CC: CurrencyConversion, L: Logger> OffersMessageFlow<MR,
998995
F: Fn(u64, u32) -> Result<(PaymentHash, PaymentSecret), Bolt12SemanticError>,
999996
{
1000997
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
998+
let conversion = &self.currency_conversion;
1001999

1002-
let amount_msats =
1003-
InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(&invoice_request.inner)?;
1000+
let amount_msats = InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
1001+
&invoice_request.inner,
1002+
conversion,
1003+
)?;
10041004

10051005
let (payment_hash, payment_secret) = get_payment_info(amount_msats, relative_expiry)?;
10061006

@@ -1021,9 +1021,10 @@ impl<MR: MessageRouter, CC: CurrencyConversion, L: Logger> OffersMessageFlow<MR,
10211021
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
10221022

10231023
#[cfg(feature = "std")]
1024-
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
1024+
let builder = invoice_request.respond_using_derived_keys(conversion, payment_paths, payment_hash);
10251025
#[cfg(not(feature = "std"))]
10261026
let builder = invoice_request.respond_using_derived_keys_no_std(
1027+
conversion,
10271028
payment_paths,
10281029
payment_hash,
10291030
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),
@@ -1057,9 +1058,12 @@ impl<MR: MessageRouter, CC: CurrencyConversion, L: Logger> OffersMessageFlow<MR,
10571058
F: Fn(u64, u32) -> Result<(PaymentHash, PaymentSecret), Bolt12SemanticError>,
10581059
{
10591060
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
1061+
let conversion = &self.currency_conversion;
10601062

1061-
let amount_msats =
1062-
InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(&invoice_request.inner)?;
1063+
let amount_msats = InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
1064+
&invoice_request.inner,
1065+
conversion,
1066+
)?;
10631067

10641068
let (payment_hash, payment_secret) = get_payment_info(amount_msats, relative_expiry)?;
10651069

@@ -1080,9 +1084,10 @@ impl<MR: MessageRouter, CC: CurrencyConversion, L: Logger> OffersMessageFlow<MR,
10801084
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
10811085

10821086
#[cfg(feature = "std")]
1083-
let builder = invoice_request.respond_with(payment_paths, payment_hash);
1087+
let builder = invoice_request.respond_with(conversion, payment_paths, payment_hash);
10841088
#[cfg(not(feature = "std"))]
10851089
let builder = invoice_request.respond_with_no_std(
1090+
conversion,
10861091
payment_paths,
10871092
payment_hash,
10881093
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),

0 commit comments

Comments
 (0)