diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index bdf67b5da7c..cfd8590a68d 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -67,7 +67,7 @@ use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSign use crate::offers::invoice_error::InvoiceError; use crate::offers::invoice_request::{DerivedPayerSigningPubkey, InvoiceRequest, InvoiceRequestBuilder}; use crate::offers::nonce::Nonce; -use crate::offers::offer::{Offer, OfferBuilder}; +use crate::offers::offer::{Amount, Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; use crate::offers::refund::{Refund, RefundBuilder}; use crate::offers::signer; @@ -1844,10 +1844,9 @@ where /// /// ``` /// # use lightning::events::{Event, EventsProvider, PaymentPurpose}; -/// # use lightning::ln::channelmanager::AChannelManager; -/// # use lightning::offers::parse::Bolt12SemanticError; +/// # use lightning::ln::channelmanager::{AChannelManager, Bolt12CreationError}; /// # -/// # fn example(channel_manager: T) -> Result<(), Bolt12SemanticError> { +/// # fn example(channel_manager: T) -> Result<(), Bolt12CreationError> { /// # let channel_manager = channel_manager.get_cm(); /// # let absolute_expiry = None; /// let offer = channel_manager @@ -1947,13 +1946,12 @@ where /// ``` /// # use core::time::Duration; /// # use lightning::events::{Event, EventsProvider}; -/// # use lightning::ln::channelmanager::{AChannelManager, PaymentId, RecentPaymentDetails, Retry}; -/// # use lightning::offers::parse::Bolt12SemanticError; +/// # use lightning::ln::channelmanager::{AChannelManager, Bolt12RequestError, PaymentId, RecentPaymentDetails, Retry}; /// # /// # fn example( /// # channel_manager: T, amount_msats: u64, absolute_expiry: Duration, retry: Retry, /// # max_total_routing_fee_msat: Option -/// # ) -> Result<(), Bolt12SemanticError> { +/// # ) -> Result<(), Bolt12RequestError> { /// # let channel_manager = channel_manager.get_cm(); /// let payment_id = PaymentId([42; 32]); /// let refund = channel_manager @@ -2685,6 +2683,40 @@ pub enum RecentPaymentDetails { }, } +/// Error during creation and handling of BOLT 12 related payments. +#[derive(Debug, Clone, PartialEq)] +pub enum Bolt12CreationError { + /// Error from BOLT 12 semantic checks. + InvalidSemantics(Bolt12SemanticError), + /// Failed to create a blinded path. + BlindedPathCreationFailed, +} + +impl From for Bolt12CreationError { + fn from(err: Bolt12SemanticError) -> Self { + Bolt12CreationError::InvalidSemantics(err) + } +} + +/// Error during requesting a BOLT 12 related payment. +#[derive(Debug, Clone, PartialEq)] +pub enum Bolt12RequestError { + /// Error from BOLT 12 semantic checks. + InvalidSemantics(Bolt12SemanticError), + /// The payment id for a refund or request is already in use. + DuplicatePaymentId, + /// There is insufficient liquidity to complete the payment. + InsufficientLiquidity, + /// Failed to create a blinded path. + BlindedPathCreationFailed, +} + +impl From for Bolt12RequestError { + fn from(err: Bolt12SemanticError) -> Self { + Bolt12RequestError::InvalidSemantics(err) + } +} + /// Route hints used in constructing invoices for [phantom node payents]. /// /// [phantom node payments]: crate::sign::PhantomKeysManager @@ -9114,7 +9146,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { /// [`InvoiceRequest`]: crate::offers::invoice_request::InvoiceRequest pub fn create_offer_builder( &$self, absolute_expiry: Option - ) -> Result<$builder, Bolt12SemanticError> { + ) -> Result<$builder, Bolt12CreationError> { let node_id = $self.get_our_node_id(); let expanded_key = &$self.inbound_payment_key; let entropy = &*$self.entropy_source; @@ -9124,7 +9156,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { let context = OffersContext::InvoiceRequest { nonce }; let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) .and_then(|paths| paths.into_iter().next().ok_or(())) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + .map_err(|()| Bolt12CreationError::BlindedPathCreationFailed)?; let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) .chain_hash($self.chain_hash) .path(path); @@ -9187,7 +9219,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { pub fn create_refund_builder( &$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option - ) -> Result<$builder, Bolt12SemanticError> { + ) -> Result<$builder, Bolt12RequestError> { let node_id = $self.get_our_node_id(); let expanded_key = &$self.inbound_payment_key; let entropy = &*$self.entropy_source; @@ -9197,7 +9229,28 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) .and_then(|paths| paths.into_iter().next().ok_or(())) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + .map_err(|()| Bolt12RequestError::BlindedPathCreationFailed)?; + + let total_liquidity: u64 = { + let per_peer_state = &$self.per_peer_state.read().unwrap(); + let mut sum: u64 = 0; + + per_peer_state.iter().for_each(|(_, peer_state_mutex)| { + let peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &*peer_state_lock; + + sum += peer_state.channel_by_id + .iter() + .map(|(_, phase)| phase.context().get_available_balances(&$self.fee_estimator).outbound_capacity_msat) + .sum::(); + }); + sum + }; + + if amount_msats > total_liquidity { + log_error!($self.logger, "Insufficient liquidity for payment with payment id: {}", payment_id); + return Err(Bolt12RequestError::InsufficientLiquidity); + } let builder = RefundBuilder::deriving_signing_pubkey( node_id, expanded_key, nonce, secp_ctx, amount_msats, payment_id @@ -9213,7 +9266,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { .add_new_awaiting_invoice( payment_id, expiration, retry_strategy, max_total_routing_fee_msat, None, ) - .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; + .map_err(|()| Bolt12RequestError::DuplicatePaymentId)?; Ok(builder.into()) } @@ -9305,7 +9358,7 @@ where &self, offer: &Offer, quantity: Option, amount_msats: Option, payer_note: Option, payment_id: PaymentId, retry_strategy: Retry, max_total_routing_fee_msat: Option - ) -> Result<(), Bolt12SemanticError> { + ) -> Result<(), Bolt12RequestError> { let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; let secp_ctx = &self.secp_ctx; @@ -9335,7 +9388,38 @@ where OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) } ); let reply_paths = self.create_blinded_paths(context) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + .map_err(|()| Bolt12RequestError::BlindedPathCreationFailed)?; + + let total_liquidity: u64 = { + let per_peer_state = &self.per_peer_state.read().unwrap(); + let mut sum: u64 = 0; + + per_peer_state.iter().for_each(|(_, peer_state_mutex)| { + let peer_state_lock = peer_state_mutex.lock().unwrap(); + let peer_state = &*peer_state_lock; + + sum += peer_state.channel_by_id + .iter() + .map(|(_, phase)| phase.context().get_available_balances(&self.fee_estimator).outbound_capacity_msat) + .sum::(); + }); + sum + }; + + let requested_amount_msats = match invoice_request.amount_msats() { + Some(amount_msats) => Some(amount_msats), + None => match offer.amount() { + Some(Amount::Bitcoin { amount_msats }) => Some(amount_msats), + _ => None, + }, + }; + + if let Some(amount) = requested_amount_msats { + if amount > total_liquidity { + log_error!(self.logger, "Insufficient liquidity for payment with payment id: {}", payment_id); + return Err(Bolt12RequestError::InsufficientLiquidity); + } + } let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -9349,16 +9433,17 @@ where payment_id, expiration, retry_strategy, max_total_routing_fee_msat, Some(retryable_invoice_request) ) - .map_err(|_| Bolt12SemanticError::DuplicatePaymentId)?; + .map_err(|()| Bolt12RequestError::DuplicatePaymentId)?; - self.enqueue_invoice_request(invoice_request, reply_paths) + self.enqueue_invoice_request(invoice_request, reply_paths)?; + Ok(()) } fn enqueue_invoice_request( &self, invoice_request: InvoiceRequest, reply_paths: Vec, - ) -> Result<(), Bolt12SemanticError> { + ) -> Result<(), Bolt12RequestError> { let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if !invoice_request.paths().is_empty() { reply_paths @@ -9384,7 +9469,7 @@ where } } else { debug_assert!(false); - return Err(Bolt12SemanticError::MissingIssuerSigningPubkey); + return Err(Bolt12RequestError::InvalidSemantics(Bolt12SemanticError::MissingIssuerSigningPubkey)); } Ok(()) @@ -9414,7 +9499,7 @@ where /// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice pub fn request_refund_payment( &self, refund: &Refund - ) -> Result { + ) -> Result { let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; let secp_ctx = &self.secp_ctx; @@ -9423,7 +9508,7 @@ where let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32; if refund.chain() != self.chain_hash { - return Err(Bolt12SemanticError::UnsupportedChain); + return Err(Bolt12CreationError::InvalidSemantics(Bolt12SemanticError::UnsupportedChain)); } let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); @@ -9434,7 +9519,7 @@ where let payment_paths = self.create_blinded_payment_paths( amount_msats, payment_secret, payment_context ) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + .map_err(|()| Bolt12CreationError::BlindedPathCreationFailed)?; #[cfg(feature = "std")] let builder = refund.respond_using_derived_keys( @@ -9457,7 +9542,7 @@ where payment_hash: invoice.payment_hash(), nonce, hmac }); let reply_paths = self.create_blinded_paths(context) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + .map_err(|()| Bolt12CreationError::BlindedPathCreationFailed)?; let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap(); if refund.paths().is_empty() { @@ -9486,7 +9571,7 @@ where Ok(invoice) }, - Err(()) => Err(Bolt12SemanticError::InvalidAmount), + Err(()) => Err(Bolt12CreationError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)), } } @@ -11135,6 +11220,13 @@ where None => return None, }; + if let Some(amount) = invoice_request.amount() { + if let Amount::Currency { .. } = amount { + let error = Bolt12SemanticError::UnsupportedCurrency; + return Some((OffersMessage::InvoiceError(error.into()), responder.respond())); + } + } + let nonce = match context { None if invoice_request.metadata().is_some() => None, Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 12b10754e41..d68392ec70a 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -48,7 +48,7 @@ use crate::blinded_path::message::BlindedMessagePath; use crate::blinded_path::payment::{Bolt12OfferContext, Bolt12RefundContext, PaymentContext}; use crate::blinded_path::message::{MessageContext, OffersContext}; use crate::events::{ClosureReason, Event, MessageSendEventsProvider, PaymentFailureReason, PaymentPurpose}; -use crate::ln::channelmanager::{Bolt12PaymentError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self}; +use crate::ln::channelmanager::{Bolt12CreationError, Bolt12PaymentError, Bolt12RequestError, MAX_SHORT_LIVED_RELATIVE_EXPIRY, PaymentId, RecentPaymentDetails, Retry, self}; use crate::ln::features::Bolt12InvoiceFeatures; use crate::ln::functional_test_utils::*; use crate::ln::inbound_payment::ExpandedKey; @@ -1681,7 +1681,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { let absolute_expiry = alice.node.duration_since_epoch() + MAX_SHORT_LIVED_RELATIVE_EXPIRY; match alice.node.create_offer_builder(Some(absolute_expiry)) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + Err(e) => assert_eq!(e, Bolt12CreationError::BlindedPathCreationFailed), } let mut args = ReconnectArgs::new(alice, bob); @@ -1697,7 +1697,7 @@ fn fails_creating_or_paying_for_offer_without_connected_peers() { match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + Err(e) => assert_eq!(e, Bolt12RequestError::BlindedPathCreationFailed), } assert!(nodes[0].node.list_recent_payments().is_empty()); @@ -1755,7 +1755,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { 10_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None ) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + Err(e) => assert_eq!(e, Bolt12RequestError::BlindedPathCreationFailed), } let mut args = ReconnectArgs::new(charlie, david); @@ -1769,7 +1769,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() { match alice.node.request_refund_payment(&refund) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + Err(e) => assert_eq!(e, Bolt12CreationError::BlindedPathCreationFailed), } let mut args = ReconnectArgs::new(alice, bob); @@ -1801,7 +1801,7 @@ fn fails_creating_invoice_request_for_unsupported_chain() { let payment_id = PaymentId([1; 32]); match bob.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), + Err(e) => assert_eq!(e, Bolt12RequestError::InvalidSemantics(Bolt12SemanticError::UnsupportedChain)), } } @@ -1828,7 +1828,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() { match alice.node.request_refund_payment(&refund) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain), + Err(e) => assert_eq!(e, Bolt12CreationError::InvalidSemantics(Bolt12SemanticError::UnsupportedChain)), } } @@ -1860,7 +1860,7 @@ fn fails_creating_invoice_request_without_blinded_reply_path() { match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + Err(e) => assert_eq!(e, Bolt12RequestError::BlindedPathCreationFailed), } assert!(nodes[0].node.list_recent_payments().is_empty()); @@ -1900,7 +1900,7 @@ fn fails_creating_invoice_request_with_duplicate_payment_id() { match david.node.pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), + Err(e) => assert_eq!(e, Bolt12RequestError::DuplicatePaymentId), } expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -1928,7 +1928,7 @@ fn fails_creating_refund_with_duplicate_payment_id() { 10_000, absolute_expiry, payment_id, Retry::Attempts(0), None ) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::DuplicatePaymentId), + Err(e) => assert_eq!(e, Bolt12RequestError::DuplicatePaymentId), } expect_recent_payment!(nodes[0], RecentPaymentDetails::AwaitingInvoice, payment_id); @@ -2051,7 +2051,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_refund() { match alice.node.request_refund_payment(&refund) { Ok(_) => panic!("Expected error"), - Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths), + Err(e) => assert_eq!(e, Bolt12CreationError::BlindedPathCreationFailed), } } @@ -2338,3 +2338,57 @@ fn no_double_pay_with_stale_channelmanager() { assert!(nodes[0].node.get_and_clear_pending_events().is_empty()); } +#[test] +fn fails_paying_offer_with_insufficient_liquidity() { + let channel_mon_config = create_chanmon_cfgs(2); + let node_config = create_node_cfgs(2, &channel_mon_config); + let node_chanmgrs = create_node_chanmgrs(2, &node_config, &[None, None]); + let nodes = create_network(2, &node_config, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let (alice, bob) = (&nodes[0], &nodes[1]); + let alice_id = alice.node.get_our_node_id(); + + let offer = alice.node + .create_offer_builder(None).unwrap() + .clear_paths() + .amount_msats(950_000_000) + .build().unwrap(); + assert_eq!(offer.issuer_signing_pubkey(), Some(alice_id)); + assert!(offer.paths().is_empty()); + + let payment_id = PaymentId([1; 32]); + + let result = bob.node + .pay_for_offer(&offer, None, None, None, payment_id, Retry::Attempts(0), None); + + match result { + Ok(_) => panic!("Expected error with insufficient liquidity."), + Err(e) => { + assert_eq!(e, Bolt12RequestError::InsufficientLiquidity); + } + } +} + +#[test] +fn fails_creating_refund_with_insufficient_liquidity() { + let chanmon_cfgs = create_chanmon_cfgs(2); + let node_cfgs = create_node_cfgs(2, &chanmon_cfgs); + let node_chanmgrs = create_node_chanmgrs(2, &node_cfgs, &[None, None]); + let nodes = create_network(2, &node_cfgs, &node_chanmgrs); + + create_announced_chan_between_nodes_with_value(&nodes, 0, 1, 10_000_000, 1_000_000_000); + + let bob = &nodes[1]; + + let absolute_expiry = Duration::from_secs(u64::MAX); + let payment_id = PaymentId([1; 32]); + let refund = bob.node + .create_refund_builder(950_000_000, absolute_expiry, payment_id, Retry::Attempts(0), None); + + match refund { + Ok(_) => panic!("Expected error with insufficient liquidity."), + Err(e) => assert_eq!(e, Bolt12RequestError::InsufficientLiquidity), + } +} \ No newline at end of file diff --git a/lightning/src/offers/invoice.rs b/lightning/src/offers/invoice.rs index 648c0fba651..966b31e2684 100644 --- a/lightning/src/offers/invoice.rs +++ b/lightning/src/offers/invoice.rs @@ -1381,11 +1381,23 @@ impl TryFrom for InvoiceContents { let refund = RefundContents::try_from( (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; + + if amount_msats != refund.amount_msats() { + return Err(Bolt12SemanticError::InvalidAmount); + } + Ok(InvoiceContents::ForRefund { refund, fields }) } else { let invoice_request = InvoiceRequestContents::try_from( (payer_tlv_stream, offer_tlv_stream, invoice_request_tlv_stream) )?; + + if let Some(requested_amount_msats) = invoice_request.amount_msats() { + if amount_msats != requested_amount_msats { + return Err(Bolt12SemanticError::InvalidAmount); + } + } + Ok(InvoiceContents::ForOffer { invoice_request, fields }) } } diff --git a/lightning/src/offers/invoice_request.rs b/lightning/src/offers/invoice_request.rs index c6c9da82a4e..f5f41d38959 100644 --- a/lightning/src/offers/invoice_request.rs +++ b/lightning/src/offers/invoice_request.rs @@ -270,6 +270,11 @@ macro_rules! invoice_request_builder_methods { ( /// Sets the [`InvoiceRequest::amount_msats`] for paying an invoice. Errors if `amount_msats` is /// not at least the expected invoice amount (i.e., [`Offer::amount`] times [`quantity`]). /// + /// When the [`Offer`] specifies a currency, this method allows setting the `amount_msats` in the + /// `InvoiceRequest`. The issuer of the Offer will validate this amount and must set a matching + /// amount in the final invoice. If the issuer disagrees with the specfified amount, the invoice + /// request will be rejected. This is also useful if the Offer doesn't specify an amount. + /// /// Successive calls to this method will override the previous setting. /// /// [`quantity`]: Self::quantity @@ -1737,6 +1742,29 @@ mod tests { Ok(_) => panic!("expected error"), Err(e) => assert_eq!(e, Bolt12SemanticError::InvalidAmount), } + + let invoice_request = OfferBuilder::new(recipient_pubkey()) + .amount(Amount::Currency { iso4217_code: *b"USD", amount: 1000 }) + .build_unchecked() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount(), Some(Amount::Currency { iso4217_code: *b"USD", amount: 1000 })); + assert_eq!(invoice_request.amount_msats(), None); + assert_eq!(tlv_stream.amount, None); + + let invoice_request = OfferBuilder::new(recipient_pubkey()) + .amount(Amount::Currency { iso4217_code: *b"USD", amount: 100 }) + .build_unchecked() + .request_invoice(vec![1; 32], payer_pubkey()).unwrap() + .amount_msats(150_000_000) + .unwrap() + .build().unwrap() + .sign(payer_sign).unwrap(); + let (_, _, tlv_stream, _) = invoice_request.as_tlv_stream(); + assert_eq!(invoice_request.amount_msats(), Some(150_000_000)); + assert_eq!(tlv_stream.amount, Some(150_000_000)); } #[test] @@ -2043,11 +2071,8 @@ mod tests { let mut buffer = Vec::new(); invoice_request.write(&mut buffer).unwrap(); - match InvoiceRequest::try_from(buffer) { - Ok(_) => panic!("expected error"), - Err(e) => { - assert_eq!(e, Bolt12ParseError::InvalidSemantics(Bolt12SemanticError::UnsupportedCurrency)); - }, + if let Err(e) = InvoiceRequest::try_from(buffer) { + panic!("error parsing invoice_request: {:?}", e); } let invoice_request = OfferBuilder::new(recipient_pubkey()) diff --git a/lightning/src/offers/offer.rs b/lightning/src/offers/offer.rs index 8501b8d4651..282b054d082 100644 --- a/lightning/src/offers/offer.rs +++ b/lightning/src/offers/offer.rs @@ -873,9 +873,8 @@ impl OfferContents { &self, amount_msats: Option, quantity: Option ) -> Result<(), Bolt12SemanticError> { let offer_amount_msats = match self.amount { - None => 0, Some(Amount::Bitcoin { amount_msats }) => amount_msats, - Some(Amount::Currency { .. }) => return Err(Bolt12SemanticError::UnsupportedCurrency), + _ => 0, }; if !self.expects_quantity() || quantity.is_some() { diff --git a/lightning/src/offers/parse.rs b/lightning/src/offers/parse.rs index a46e90de6be..3be98108cbc 100644 --- a/lightning/src/offers/parse.rs +++ b/lightning/src/offers/parse.rs @@ -147,6 +147,9 @@ pub enum Bolt12SemanticError { /// An amount was expected but was missing. MissingAmount, /// The amount exceeded the total bitcoin supply. + /// + /// This error can also occur if the amount in an invoice does not match the expected + /// amount specified in the associated `InvoiceRequest` or `Refund`. InvalidAmount, /// An amount was provided but was not sufficient in value. InsufficientAmount, @@ -178,8 +181,8 @@ pub enum Bolt12SemanticError { MissingPayerMetadata, /// A payer signing pubkey was expected but was missing. MissingPayerSigningPubkey, - /// The payment id for a refund or request is already in use. - DuplicatePaymentId, + /// A payer id was expected but was missing. + MissingPayerId, /// Blinded paths were expected but were missing. MissingPaths, /// Blinded paths were provided but were not expected.