-
Notifications
You must be signed in to change notification settings - Fork 410
Various BOLT11 payment interface fixes #3810
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7175ba4
864271c
9dd44cb
b4da06c
b655d10
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -588,12 +588,15 @@ pub(crate) enum PaymentSendFailure { | |
#[derive(Debug)] | ||
pub enum Bolt11PaymentError { | ||
/// Incorrect amount was provided to [`ChannelManager::pay_for_bolt11_invoice`]. | ||
/// This happens when an amount is specified when [`Bolt11Invoice`] already contains | ||
/// an amount, or vice versa. | ||
/// This happens when the user-provided amount is less than an amount specified in the [`Bolt11Invoice`]. | ||
/// | ||
/// [`Bolt11Invoice`]: lightning_invoice::Bolt11Invoice | ||
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice | ||
InvalidAmount, | ||
/// An invalid or incompatible invoice was provided to [`ChannelManager::pay_for_bolt11_invoice`]. | ||
/// | ||
/// [`ChannelManager::pay_for_bolt11_invoice`]: crate::ln::channelmanager::ChannelManager::pay_for_bolt11_invoice | ||
InvalidInvoice, | ||
/// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. | ||
SendingFailed(RetryableSendFailure), | ||
} | ||
|
@@ -605,6 +608,10 @@ pub enum Bolt12PaymentError { | |
UnexpectedInvoice, | ||
/// Payment for an invoice with the corresponding [`PaymentId`] was already initiated. | ||
DuplicateInvoice, | ||
/// An invalid or incompatible invoice was provided to [`ChannelManager`]. | ||
/// | ||
/// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager | ||
InvalidInvoice, | ||
Comment on lines
+611
to
+614
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Likewise. |
||
/// The invoice was valid for the corresponding [`PaymentId`], but required unknown features. | ||
UnknownRequiredFeatures, | ||
/// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed. | ||
|
@@ -894,13 +901,16 @@ impl OutboundPayments { | |
|
||
let amount = match (invoice.amount_milli_satoshis(), amount_msats) { | ||
(Some(amt), None) | (None, Some(amt)) => amt, | ||
(None, None) | (Some(_), Some(_)) => return Err(Bolt11PaymentError::InvalidAmount), | ||
(Some(inv_amt), Some(user_amt)) if user_amt < inv_amt => return Err(Bolt11PaymentError::InvalidAmount), | ||
(Some(_), Some(user_amt)) => user_amt, | ||
(None, None) => return Err(Bolt11PaymentError::InvalidAmount), | ||
}; | ||
|
||
let mut recipient_onion = RecipientOnionFields::secret_only(*invoice.payment_secret()); | ||
recipient_onion.payment_metadata = invoice.payment_metadata().map(|v| v.clone()); | ||
|
||
let payment_params = PaymentParameters::from_bolt11_invoice(invoice) | ||
.map_err(|_| Bolt11PaymentError::InvalidInvoice)? | ||
.with_user_config_ignoring_fee_limit(route_params_config); | ||
|
||
let mut route_params = RouteParameters::from_payment_params_and_value(payment_params, amount); | ||
|
@@ -948,6 +958,7 @@ impl OutboundPayments { | |
|
||
let mut route_params = RouteParameters::from_payment_params_and_value( | ||
PaymentParameters::from_bolt12_invoice(&invoice) | ||
.map_err(|_| Bolt12PaymentError::InvalidInvoice)? | ||
.with_user_config_ignoring_fee_limit(params_config), invoice.amount_msats() | ||
); | ||
if let Some(max_fee_msat) = params_config.max_total_routing_fee_msat { | ||
|
@@ -1126,6 +1137,7 @@ impl OutboundPayments { | |
let keysend_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes()); | ||
let payment_hash = PaymentHash(Sha256::hash(&keysend_preimage.0).to_byte_array()); | ||
let pay_params = PaymentParameters::from_static_invoice(invoice) | ||
.map_err(|_| Bolt12PaymentError::InvalidInvoice)? | ||
.with_user_config_ignoring_fee_limit(*route_params_config); | ||
let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat); | ||
route_params.max_total_routing_fee_msat = route_params_config.max_total_routing_fee_msat; | ||
|
@@ -3033,7 +3045,7 @@ mod tests { | |
assert!(outbound_payments.has_pending_payments()); | ||
|
||
let route_params = RouteParameters::from_payment_params_and_value( | ||
PaymentParameters::from_bolt12_invoice(&invoice), | ||
PaymentParameters::from_bolt12_invoice(&invoice).unwrap(), | ||
invoice.amount_msats(), | ||
); | ||
router.expect_find_route(route_params, Err("")); | ||
|
@@ -3085,7 +3097,7 @@ mod tests { | |
.sign(recipient_sign).unwrap(); | ||
|
||
let route_params = RouteParameters { | ||
payment_params: PaymentParameters::from_bolt12_invoice(&invoice), | ||
payment_params: PaymentParameters::from_bolt12_invoice(&invoice).unwrap(), | ||
final_value_msat: invoice.amount_msats(), | ||
max_total_routing_fee_msat: Some(1234), | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -921,41 +921,46 @@ impl PaymentParameters { | |
/// Creates parameters for paying to a blinded payee from the provided invoice. Sets | ||
/// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and | ||
/// [`PaymentParameters::expiry_time`]. | ||
pub fn from_bolt11_invoice(invoice: &Bolt11Invoice) -> Self { | ||
/// | ||
/// Will return an error if an incompatible invoice is provided. | ||
pub fn from_bolt11_invoice(invoice: &Bolt11Invoice) -> Result<Self, ()>{ | ||
let mut payment_params = Self::from_node_id( | ||
invoice.recover_payee_pub_key(), | ||
invoice.min_final_cltv_expiry_delta() as u32, | ||
) | ||
.with_route_hints(invoice.route_hints()) | ||
.unwrap(); | ||
.with_route_hints(invoice.route_hints())?; | ||
|
||
if let Some(expiry) = invoice.expires_at() { | ||
payment_params = payment_params.with_expiry_time(expiry.as_secs()); | ||
} | ||
if let Some(features) = invoice.features() { | ||
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap(); | ||
payment_params = payment_params.with_bolt11_features(features.clone())?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, but neither of these calls an fail, right? Why bother returning a result for an infallible operation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not currently. But it just takes one added validation step on the user input in another part of the code for this to transform into a reachable panic. IMO we should get rid of any |
||
} | ||
|
||
payment_params | ||
Ok(payment_params) | ||
} | ||
|
||
/// Creates parameters for paying to a blinded payee from the provided invoice. Sets | ||
/// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and | ||
/// [`PaymentParameters::expiry_time`]. | ||
pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Self { | ||
Self::blinded(invoice.payment_paths().to_vec()) | ||
.with_bolt12_features(invoice.invoice_features().clone()).unwrap() | ||
.with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs())) | ||
/// | ||
/// Will return an error if an incompatible invoice is provided. | ||
pub fn from_bolt12_invoice(invoice: &Bolt12Invoice) -> Result<Self, ()> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Honestly, why is this public to begin with? Presumably no one should ever use it directly? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the I think we'd currently also need it to send probes for a |
||
Ok(Self::blinded(invoice.payment_paths().to_vec()) | ||
.with_bolt12_features(invoice.invoice_features().clone())? | ||
.with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()))) | ||
} | ||
|
||
/// Creates parameters for paying to a blinded payee from the provided invoice. Sets | ||
/// [`Payee::Blinded::route_hints`], [`Payee::Blinded::features`], and | ||
/// [`PaymentParameters::expiry_time`]. | ||
/// | ||
/// Will return an error if an incompatible invoice is provided. | ||
#[cfg(async_payments)] | ||
pub fn from_static_invoice(invoice: &StaticInvoice) -> Self { | ||
Self::blinded(invoice.payment_paths().to_vec()) | ||
.with_bolt12_features(invoice.invoice_features().clone()).unwrap() | ||
.with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs())) | ||
pub fn from_static_invoice(invoice: &StaticInvoice) -> Result<Self, ()> { | ||
Ok(Self::blinded(invoice.payment_paths().to_vec()) | ||
.with_bolt12_features(invoice.invoice_features().clone())? | ||
.with_expiry_time(invoice.created_at().as_secs().saturating_add(invoice.relative_expiry().as_secs()))) | ||
} | ||
|
||
/// Creates parameters for paying to a blinded payee from the provided blinded route hints. | ||
|
@@ -1075,7 +1080,7 @@ impl PaymentParameters { | |
} | ||
|
||
/// A struct for configuring parameters for routing the payment. | ||
#[derive(Clone, Copy)] | ||
#[derive(Clone, Copy, Debug)] | ||
pub struct RouteParametersConfig { | ||
/// The maximum total fees, in millisatoshi, that may accrue during route finding. | ||
/// | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we want to add this if it will never be created? It should be an invariant of
from_bolt11_invoice
that it will never fail. We can change theunwrap
s toexpect
s, IMO.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I agree these are not reachable at the moment, but it's pretty risky to have
unwrap
s andexpect
s sprinkled over the codebase that could get reachable, e.g., by a refactoring in the future (it would just take introducing one more error condition that depends on the user input inwith_route_hints
orwith_{bolt11,bolt12}_features
and we'd get reachable panics in the correspondingfrom_
method(s)).IMO,
unwrap
s (outside of trivial ones like onlock
), should generally be avoided.If you dislike the extra error variant I guess we could also see if there are other ways to make this safer (more comments, debug_asserts, maybe tests), or through the type system, so open for suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, ideally through the type system where the
Payee
enum is pulled out ofPaymentParameters
. Probably something like:And where
RouteParameters
holds aPayee
instead ofPaymentParameters
.