Skip to content

Commit 528db99

Browse files
committed
Integrate CurrencyConversion into Bolt12Invoice amount handling
This commit updates the Bolt12Invoice amount creation logic to utilize the `CurrencyConversion` trait, enabling more flexible and customizable handling of fiat-to-msat conversions. Reasoning The `CurrencyConversion` trait is passed upstream into the invoice's amount creation flow, where it is used to interpret the Offer’s currency amount (if present) into millisatoshis. This change establishes a unified mechanism for amount handling—regardless of whether the Offer’s amount is denominated in Bitcoin or fiat, or whether the InvoiceRequest specifies an amount or not.
1 parent 3ed36a7 commit 528db99

File tree

8 files changed

+262
-114
lines changed

8 files changed

+262
-114
lines changed

fuzz/src/invoice_request_deser.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ use lightning::blinded_path::payment::{
1717
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
1818
use lightning::ln::inbound_payment::ExpandedKey;
1919
use lightning::offers::invoice::UnsignedBolt12Invoice;
20-
use lightning::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields};
21-
use lightning::offers::offer::OfferId;
20+
use lightning::offers::invoice_request::{
21+
CurrencyConversion, InvoiceRequest, InvoiceRequestFields,
22+
};
23+
use lightning::offers::offer::{CurrencyCode, OfferId};
2224
use lightning::offers::parse::Bolt12SemanticError;
2325
use lightning::sign::{EntropySource, ReceiveAuthKey};
2426
use lightning::types::features::BlindedHopFeatures;
@@ -78,6 +80,14 @@ fn privkey(byte: u8) -> SecretKey {
7880
SecretKey::from_slice(&[byte; 32]).unwrap()
7981
}
8082

83+
struct FuzzCurrencyConversion;
84+
85+
impl CurrencyConversion for FuzzCurrencyConversion {
86+
fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result<u64, ()> {
87+
unreachable!()
88+
}
89+
}
90+
8191
fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
8292
invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>,
8393
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
@@ -144,7 +154,7 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
144154
.unwrap();
145155

146156
let payment_hash = PaymentHash([42; 32]);
147-
invoice_request.respond_with(vec![payment_path], payment_hash)?.build()
157+
invoice_request.respond_with(&FuzzCurrencyConversion, vec![payment_path], payment_hash)?.build()
148158
}
149159

150160
pub fn invoice_request_deser_test<Out: test_logger::Output>(data: &[u8], out: Out) {

lightning/src/ln/channelmanager.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,9 @@ use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
9393
use crate::offers::flow::{HeldHtlcReplyPath, InvreqResponseInstructions, OffersMessageFlow};
9494
use crate::offers::invoice::{Bolt12Invoice, UnsignedBolt12Invoice};
9595
use crate::offers::invoice_error::InvoiceError;
96-
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestVerifiedFromOffer};
96+
use crate::offers::invoice_request::{
97+
DefaultCurrencyConversion, InvoiceRequest, InvoiceRequestVerifiedFromOffer,
98+
};
9799
use crate::offers::nonce::Nonce;
98100
use crate::offers::offer::{Offer, OfferFromHrn};
99101
use crate::offers::parse::Bolt12SemanticError;
@@ -5666,6 +5668,7 @@ where
56665668
let features = self.bolt12_invoice_features();
56675669
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
56685670
invoice,
5671+
&DefaultCurrencyConversion,
56695672
payment_id,
56705673
features,
56715674
best_block_height,
@@ -15320,6 +15323,7 @@ where
1532015323
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
1532115324
let result = self.flow.create_invoice_builder_from_invoice_request_with_keys(
1532215325
&self.router,
15326+
&DefaultCurrencyConversion,
1532315327
&request,
1532415328
self.list_usable_channels(),
1532515329
get_payment_info,
@@ -15344,6 +15348,7 @@ where
1534415348
InvoiceRequestVerifiedFromOffer::ExplicitKeys(request) => {
1534515349
let result = self.flow.create_invoice_builder_from_invoice_request_without_keys(
1534615350
&self.router,
15351+
&DefaultCurrencyConversion,
1534715352
&request,
1534815353
self.list_usable_channels(),
1534915354
get_payment_info,

lightning/src/ln/offers_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, Init, NodeAnnou
5757
use crate::ln::outbound_payment::IDEMPOTENCY_TIMEOUT_TICKS;
5858
use crate::offers::invoice::Bolt12Invoice;
5959
use crate::offers::invoice_error::InvoiceError;
60-
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields, InvoiceRequestVerifiedFromOffer};
60+
use crate::offers::invoice_request::{DefaultCurrencyConversion, InvoiceRequest, InvoiceRequestFields, InvoiceRequestVerifiedFromOffer};
6161
use crate::offers::nonce::Nonce;
6262
use crate::offers::parse::Bolt12SemanticError;
6363
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageSendInstructions, NodeIdMessageRouter, NullMessageRouter, PeeledOnion, PADDED_PATH_LENGTH};
@@ -2331,7 +2331,7 @@ fn fails_paying_invoice_with_unknown_required_features() {
23312331

23322332
let invoice = match verified_invoice_request {
23332333
InvoiceRequestVerifiedFromOffer::DerivedKeys(request) => {
2334-
request.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
2334+
request.respond_using_derived_keys_no_std(&DefaultCurrencyConversion, payment_paths, payment_hash, created_at).unwrap()
23352335
.features_unchecked(Bolt12InvoiceFeatures::unknown())
23362336
.build_and_sign(&secp_ctx).unwrap()
23372337
},

lightning/src/ln/outbound_payment.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use crate::ln::channelmanager::{
2323
use crate::ln::onion_utils;
2424
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
2525
use crate::offers::invoice::{Bolt12Invoice, DerivedSigningPubkey, InvoiceBuilder};
26+
use crate::offers::invoice_request::CurrencyConversion;
2627
use crate::offers::invoice_request::InvoiceRequest;
2728
use crate::offers::nonce::Nonce;
2829
use crate::offers::static_invoice::StaticInvoice;
@@ -1115,13 +1116,15 @@ where
11151116
Ok(())
11161117
}
11171118

1118-
pub(super) fn static_invoice_received<ES: Deref>(
1119-
&self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures,
1120-
best_block_height: u32, duration_since_epoch: Duration, entropy_source: ES,
1119+
pub(super) fn static_invoice_received<ES: Deref, CC: Deref>(
1120+
&self, invoice: &StaticInvoice, currency_conversion: CC, payment_id: PaymentId,
1121+
features: Bolt12InvoiceFeatures, best_block_height: u32, duration_since_epoch: Duration,
1122+
entropy_source: ES,
11211123
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
11221124
) -> Result<(), Bolt12PaymentError>
11231125
where
11241126
ES::Target: EntropySource,
1127+
CC::Target: CurrencyConversion,
11251128
{
11261129
macro_rules! abandon_with_entry {
11271130
($payment: expr, $reason: expr) => {
@@ -1168,6 +1171,7 @@ where
11681171

11691172
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
11701173
invreq,
1174+
currency_conversion,
11711175
) {
11721176
Ok(amt) => amt,
11731177
Err(_) => {
@@ -3206,7 +3210,7 @@ mod tests {
32063210
.build().unwrap()
32073211
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
32083212
.build_and_sign().unwrap()
3209-
.respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap()
3213+
.respond_with_no_conversion(payment_paths(), payment_hash(), created_at).unwrap()
32103214
.build().unwrap()
32113215
.sign(recipient_sign).unwrap();
32123216

@@ -3253,7 +3257,7 @@ mod tests {
32533257
.build().unwrap()
32543258
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
32553259
.build_and_sign().unwrap()
3256-
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
3260+
.respond_with_no_conversion(payment_paths(), payment_hash(), now()).unwrap()
32573261
.build().unwrap()
32583262
.sign(recipient_sign).unwrap();
32593263

@@ -3316,7 +3320,7 @@ mod tests {
33163320
.build().unwrap()
33173321
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
33183322
.build_and_sign().unwrap()
3319-
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
3323+
.respond_with_no_conversion(payment_paths(), payment_hash(), now()).unwrap()
33203324
.build().unwrap()
33213325
.sign(recipient_sign).unwrap();
33223326

lightning/src/offers/flow.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ use crate::offers::invoice::{
4040
DEFAULT_RELATIVE_EXPIRY,
4141
};
4242
use crate::offers::invoice_request::{
43-
InvoiceRequest, InvoiceRequestBuilder, InvoiceRequestVerifiedFromOffer, VerifiedInvoiceRequest,
43+
CurrencyConversion, InvoiceRequest, InvoiceRequestBuilder, InvoiceRequestVerifiedFromOffer,
44+
VerifiedInvoiceRequest,
4445
};
4546
use crate::offers::nonce::Nonce;
4647
use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder};
@@ -961,18 +962,23 @@ where
961962
/// Returns a [`Bolt12SemanticError`] if:
962963
/// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`].
963964
/// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`].
964-
pub fn create_invoice_builder_from_invoice_request_with_keys<'a, R: Deref, F>(
965-
&self, router: &R, invoice_request: &'a VerifiedInvoiceRequest<DerivedSigningPubkey>,
965+
pub fn create_invoice_builder_from_invoice_request_with_keys<'a, R: Deref, F, CC: Deref>(
966+
&self, router: &R, currency_conversion: CC,
967+
invoice_request: &'a VerifiedInvoiceRequest<DerivedSigningPubkey>,
966968
usable_channels: Vec<ChannelDetails>, get_payment_info: F,
967969
) -> Result<(InvoiceBuilder<'a, DerivedSigningPubkey>, MessageContext), Bolt12SemanticError>
968970
where
969971
R::Target: Router,
972+
CC::Target: CurrencyConversion,
970973
F: Fn(u64, u32) -> Result<(PaymentHash, PaymentSecret), Bolt12SemanticError>,
971974
{
975+
let conversion = &*currency_conversion;
972976
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
973977

974-
let amount_msats =
975-
InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(&invoice_request.inner)?;
978+
let amount_msats = InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
979+
&invoice_request.inner,
980+
conversion,
981+
)?;
976982

977983
let (payment_hash, payment_secret) = get_payment_info(amount_msats, relative_expiry)?;
978984

@@ -993,9 +999,10 @@ where
993999
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
9941000

9951001
#[cfg(feature = "std")]
996-
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
1002+
let builder = invoice_request.respond_using_derived_keys(conversion, payment_paths, payment_hash);
9971003
#[cfg(not(feature = "std"))]
9981004
let builder = invoice_request.respond_using_derived_keys_no_std(
1005+
conversion,
9991006
payment_paths,
10001007
payment_hash,
10011008
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),
@@ -1021,18 +1028,23 @@ where
10211028
/// Returns a [`Bolt12SemanticError`] if:
10221029
/// - Valid blinded payment paths could not be generated for the [`Bolt12Invoice`].
10231030
/// - The [`InvoiceBuilder`] could not be created from the [`InvoiceRequest`].
1024-
pub fn create_invoice_builder_from_invoice_request_without_keys<'a, R: Deref, F>(
1025-
&self, router: &R, invoice_request: &'a VerifiedInvoiceRequest<ExplicitSigningPubkey>,
1031+
pub fn create_invoice_builder_from_invoice_request_without_keys<'a, R: Deref, F, CC: Deref>(
1032+
&self, router: &R, currency_conversion: CC,
1033+
invoice_request: &'a VerifiedInvoiceRequest<ExplicitSigningPubkey>,
10261034
usable_channels: Vec<ChannelDetails>, get_payment_info: F,
10271035
) -> Result<(InvoiceBuilder<'a, ExplicitSigningPubkey>, MessageContext), Bolt12SemanticError>
10281036
where
10291037
R::Target: Router,
1038+
CC::Target: CurrencyConversion,
10301039
F: Fn(u64, u32) -> Result<(PaymentHash, PaymentSecret), Bolt12SemanticError>,
10311040
{
1041+
let conversion = &*currency_conversion;
10321042
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
10331043

1034-
let amount_msats =
1035-
InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(&invoice_request.inner)?;
1044+
let amount_msats = InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
1045+
&invoice_request.inner,
1046+
conversion,
1047+
)?;
10361048

10371049
let (payment_hash, payment_secret) = get_payment_info(amount_msats, relative_expiry)?;
10381050

@@ -1053,9 +1065,10 @@ where
10531065
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
10541066

10551067
#[cfg(feature = "std")]
1056-
let builder = invoice_request.respond_with(payment_paths, payment_hash);
1068+
let builder = invoice_request.respond_with(conversion, payment_paths, payment_hash);
10571069
#[cfg(not(feature = "std"))]
10581070
let builder = invoice_request.respond_with_no_std(
1071+
conversion,
10591072
payment_paths,
10601073
payment_hash,
10611074
Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64),

0 commit comments

Comments
 (0)