Skip to content

Commit f10ac51

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 5fea57e commit f10ac51

File tree

7 files changed

+364
-111
lines changed

7 files changed

+364
-111
lines changed

fuzz/src/invoice_request_deser.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@
1010
use crate::utils::test_logger;
1111
use bitcoin::secp256k1::{self, Keypair, Parity, PublicKey, Secp256k1, SecretKey};
1212
use core::convert::TryFrom;
13+
use core::ops::Deref;
1314
use lightning::blinded_path::payment::{
1415
BlindedPaymentPath, Bolt12OfferContext, ForwardTlvs, PaymentConstraints, PaymentContext,
1516
PaymentForwardNode, PaymentRelay, UnauthenticatedReceiveTlvs,
1617
};
1718
use lightning::ln::channelmanager::MIN_FINAL_CLTV_EXPIRY_DELTA;
1819
use lightning::ln::inbound_payment::ExpandedKey;
1920
use lightning::offers::invoice::UnsignedBolt12Invoice;
20-
use lightning::offers::invoice_request::{InvoiceRequest, InvoiceRequestFields};
21+
use lightning::offers::invoice_request::{
22+
CurrencyConversion, InvoiceRequest, InvoiceRequestFields,
23+
};
2124
use lightning::offers::nonce::Nonce;
22-
use lightning::offers::offer::OfferId;
25+
use lightning::offers::offer::{CurrencyCode, OfferId};
2326
use lightning::offers::parse::Bolt12SemanticError;
2427
use lightning::sign::EntropySource;
2528
use lightning::types::features::BlindedHopFeatures;
@@ -79,6 +82,22 @@ fn privkey(byte: u8) -> SecretKey {
7982
SecretKey::from_slice(&[byte; 32]).unwrap()
8083
}
8184

85+
struct FuzzCurrencyConversion;
86+
87+
impl Deref for FuzzCurrencyConversion {
88+
type Target = Self;
89+
90+
fn deref(&self) -> &Self::Target {
91+
self
92+
}
93+
}
94+
95+
impl CurrencyConversion for FuzzCurrencyConversion {
96+
fn fiat_to_msats(&self, _iso4217_code: CurrencyCode) -> Result<u64, Bolt12SemanticError> {
97+
unreachable!()
98+
}
99+
}
100+
82101
fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
83102
invoice_request: &InvoiceRequest, secp_ctx: &Secp256k1<T>,
84103
) -> Result<UnsignedBolt12Invoice, Bolt12SemanticError> {
@@ -145,7 +164,9 @@ fn build_response<T: secp256k1::Signing + secp256k1::Verification>(
145164
.unwrap();
146165

147166
let payment_hash = PaymentHash([42; 32]);
148-
invoice_request.respond_with(vec![payment_path], payment_hash)?.build()
167+
invoice_request
168+
.respond_with(&FuzzCurrencyConversion {}, vec![payment_path], payment_hash)?
169+
.build()
149170
}
150171

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

lightning/src/ln/channelmanager.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5283,6 +5283,7 @@ where
52835283
let features = self.bolt12_invoice_features();
52845284
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
52855285
invoice,
5286+
&self.flow.currency_conversion,
52865287
payment_id,
52875288
features,
52885289
best_block_height,
@@ -14214,7 +14215,7 @@ where
1421414215
};
1421514216

1421614217
let amount_msats = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(
14217-
&invoice_request.inner
14218+
&invoice_request.inner, &self.flow.currency_conversion
1421814219
) {
1421914220
Ok(amount_msats) => amount_msats,
1422014221
Err(error) => return Some((OffersMessage::InvoiceError(error.into()), responder.respond())),

lightning/src/ln/offers_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2281,7 +2281,7 @@ fn fails_paying_invoice_with_unknown_required_features() {
22812281
let created_at = alice.node.duration_since_epoch();
22822282
let invoice = invoice_request
22832283
.verify_using_recipient_data(nonce, &expanded_key, &secp_ctx).unwrap()
2284-
.respond_using_derived_keys_no_std(payment_paths, payment_hash, created_at).unwrap()
2284+
.respond_using_derived_keys_no_std(&alice.node.flow.currency_conversion, payment_paths, payment_hash, created_at).unwrap()
22852285
.features_unchecked(Bolt12InvoiceFeatures::unknown())
22862286
.build_and_sign(&secp_ctx).unwrap();
22872287

lightning/src/ln/outbound_payment.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ use crate::ln::channelmanager::{EventCompletionAction, HTLCSource, PaymentId};
2121
use crate::ln::onion_utils;
2222
use crate::ln::onion_utils::{DecodedOnionFailure, HTLCFailReason};
2323
use crate::offers::invoice::Bolt12Invoice;
24+
#[cfg(async_payments)]
25+
use crate::offers::invoice_request::CurrencyConversion;
2426
use crate::offers::invoice_request::InvoiceRequest;
2527
use crate::offers::nonce::Nonce;
2628
use crate::offers::static_invoice::StaticInvoice;
@@ -1113,11 +1115,11 @@ impl OutboundPayments {
11131115

11141116
#[cfg(async_payments)]
11151117
#[rustfmt::skip]
1116-
pub(super) fn static_invoice_received<ES: Deref>(
1117-
&self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures,
1118+
pub(super) fn static_invoice_received<ES: Deref, CC: Deref>(
1119+
&self, invoice: &StaticInvoice, currency_conversion: &CC, payment_id: PaymentId, features: Bolt12InvoiceFeatures,
11181120
best_block_height: u32, duration_since_epoch: Duration, entropy_source: ES,
11191121
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>
1120-
) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource {
1122+
) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource, CC::Target: CurrencyConversion {
11211123
macro_rules! abandon_with_entry {
11221124
($payment: expr, $reason: expr) => {
11231125
assert!(
@@ -1154,7 +1156,7 @@ impl OutboundPayments {
11541156
return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::PaymentExpired))
11551157
}
11561158

1157-
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(invreq) {
1159+
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(invreq, currency_conversion) {
11581160
Ok(amt) => amt,
11591161
Err(_) => {
11601162
// We check this during invoice request parsing, when constructing the invreq's
@@ -2758,7 +2760,7 @@ mod tests {
27582760
};
27592761
#[cfg(feature = "std")]
27602762
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
2761-
use crate::offers::invoice_request::InvoiceRequest;
2763+
use crate::offers::invoice_request::{DefaultCurrencyConversion, InvoiceRequest};
27622764
use crate::offers::nonce::Nonce;
27632765
use crate::offers::offer::OfferBuilder;
27642766
use crate::offers::test_utils::*;
@@ -3141,7 +3143,7 @@ mod tests {
31413143
.build().unwrap()
31423144
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
31433145
.build_and_sign().unwrap()
3144-
.respond_with_no_std(payment_paths(), payment_hash(), created_at).unwrap()
3146+
.respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths(), payment_hash(), created_at).unwrap()
31453147
.build().unwrap()
31463148
.sign(recipient_sign).unwrap();
31473149

@@ -3188,7 +3190,7 @@ mod tests {
31883190
.build().unwrap()
31893191
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
31903192
.build_and_sign().unwrap()
3191-
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
3193+
.respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths(), payment_hash(), now()).unwrap()
31923194
.build().unwrap()
31933195
.sign(recipient_sign).unwrap();
31943196

@@ -3251,7 +3253,7 @@ mod tests {
32513253
.build().unwrap()
32523254
.request_invoice(&expanded_key, nonce, &secp_ctx, payment_id).unwrap()
32533255
.build_and_sign().unwrap()
3254-
.respond_with_no_std(payment_paths(), payment_hash(), now()).unwrap()
3256+
.respond_with_no_std(&DefaultCurrencyConversion {}, payment_paths(), payment_hash(), now()).unwrap()
32553257
.build().unwrap()
32563258
.sign(recipient_sign).unwrap();
32573259

lightning/src/offers/flow.rs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::offers::invoice::{
4141
};
4242
use crate::offers::invoice_error::InvoiceError;
4343
use crate::offers::invoice_request::{
44-
InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest,
44+
DefaultCurrencyConversion, InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest,
4545
};
4646
use crate::offers::nonce::Nonce;
4747
use crate::offers::offer::{DerivedMetadata, Offer, OfferBuilder};
@@ -97,6 +97,8 @@ where
9797
secp_ctx: Secp256k1<secp256k1::All>,
9898
message_router: MR,
9999

100+
pub(crate) currency_conversion: DefaultCurrencyConversion,
101+
100102
#[cfg(not(any(test, feature = "_test_utils")))]
101103
pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
102104
#[cfg(any(test, feature = "_test_utils"))]
@@ -137,6 +139,8 @@ where
137139
secp_ctx,
138140
message_router,
139141

142+
currency_conversion: DefaultCurrencyConversion {},
143+
140144
pending_offers_messages: Mutex::new(Vec::new()),
141145
pending_async_payments_messages: Mutex::new(Vec::new()),
142146

@@ -968,9 +972,14 @@ where
968972

969973
let response = if invoice_request.keys.is_some() {
970974
#[cfg(feature = "std")]
971-
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
975+
let builder = invoice_request.respond_using_derived_keys(
976+
&self.currency_conversion,
977+
payment_paths,
978+
payment_hash,
979+
);
972980
#[cfg(not(feature = "std"))]
973981
let builder = invoice_request.respond_using_derived_keys_no_std(
982+
&self.currency_conversion,
974983
payment_paths,
975984
payment_hash,
976985
created_at,
@@ -981,9 +990,14 @@ where
981990
.map_err(InvoiceError::from)
982991
} else {
983992
#[cfg(feature = "std")]
984-
let builder = invoice_request.respond_with(payment_paths, payment_hash);
993+
let builder = invoice_request.respond_with(&self.currency_conversion, payment_paths, payment_hash);
985994
#[cfg(not(feature = "std"))]
986-
let builder = invoice_request.respond_with_no_std(payment_paths, payment_hash, created_at);
995+
let builder = invoice_request.respond_with_no_std(
996+
&self.currency_conversion,
997+
payment_paths,
998+
payment_hash,
999+
created_at,
1000+
);
9871001
builder
9881002
.map(InvoiceBuilder::<ExplicitSigningPubkey>::from)
9891003
.and_then(|builder| builder.allow_mpp().build())

0 commit comments

Comments
 (0)