Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 115 additions & 23 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12SemanticError> {
/// # fn example<T: AChannelManager>(channel_manager: T) -> Result<(), Bolt12CreationError> {
/// # let channel_manager = channel_manager.get_cm();
/// # let absolute_expiry = None;
/// let offer = channel_manager
Expand Down Expand Up @@ -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<T: AChannelManager>(
/// # channel_manager: T, amount_msats: u64, absolute_expiry: Duration, retry: Retry,
/// # max_total_routing_fee_msat: Option<u64>
/// # ) -> Result<(), Bolt12SemanticError> {
/// # ) -> Result<(), Bolt12RequestError> {
/// # let channel_manager = channel_manager.get_cm();
/// let payment_id = PaymentId([42; 32]);
/// let refund = channel_manager
Expand Down Expand Up @@ -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<Bolt12SemanticError> 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<Bolt12SemanticError> 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
Expand Down Expand Up @@ -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<Duration>
) -> 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;
Expand All @@ -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);
Expand Down Expand Up @@ -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<u64>
) -> 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;
Expand All @@ -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::<u64>();
});
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
Expand All @@ -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())
}
Expand Down Expand Up @@ -9305,7 +9358,7 @@ where
&self, offer: &Offer, quantity: Option<u64>, amount_msats: Option<u64>,
payer_note: Option<String>, payment_id: PaymentId, retry_strategy: Retry,
max_total_routing_fee_msat: Option<u64>
) -> Result<(), Bolt12SemanticError> {
) -> Result<(), Bolt12RequestError> {
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
Expand Down Expand Up @@ -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::<u64>();
});
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);

Expand All @@ -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<BlindedMessagePath>,
) -> Result<(), Bolt12SemanticError> {
) -> Result<(), Bolt12RequestError> {
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if !invoice_request.paths().is_empty() {
reply_paths
Expand All @@ -9384,7 +9469,7 @@ where
}
} else {
debug_assert!(false);
return Err(Bolt12SemanticError::MissingIssuerSigningPubkey);
return Err(Bolt12RequestError::InvalidSemantics(Bolt12SemanticError::MissingIssuerSigningPubkey));
}

Ok(())
Expand Down Expand Up @@ -9414,7 +9499,7 @@ where
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn request_refund_payment(
&self, refund: &Refund
) -> Result<Bolt12Invoice, Bolt12SemanticError> {
) -> Result<Bolt12Invoice, Bolt12CreationError> {
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;
Expand All @@ -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);
Expand All @@ -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(
Expand All @@ -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() {
Expand Down Expand Up @@ -9486,7 +9571,7 @@ where

Ok(invoice)
},
Err(()) => Err(Bolt12SemanticError::InvalidAmount),
Err(()) => Err(Bolt12CreationError::InvalidSemantics(Bolt12SemanticError::InvalidAmount)),
}
}

Expand Down Expand Up @@ -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),
Expand Down
Loading