mirror of
https://github.com/lightningdevkit/rust-lightning.git
synced 2025-03-15 15:39:09 +01:00
Merge pull request #3140 from valentinewallace/2024-06-pay-static-invoice
Support paying static invoices
This commit is contained in:
commit
22146a98df
13 changed files with 687 additions and 141 deletions
|
@ -5,7 +5,9 @@ use bitcoin::secp256k1::ecdsa::RecoverableSignature;
|
|||
use bitcoin::secp256k1::schnorr;
|
||||
use bitcoin::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey};
|
||||
|
||||
use lightning::blinded_path::message::{BlindedMessagePath, MessageContext, OffersContext};
|
||||
use lightning::blinded_path::message::{
|
||||
AsyncPaymentsContext, BlindedMessagePath, MessageContext, OffersContext,
|
||||
};
|
||||
use lightning::blinded_path::EmptyNodeIdLookUp;
|
||||
use lightning::ln::features::InitFeatures;
|
||||
use lightning::ln::msgs::{self, DecodeError, OnionMessageHandler};
|
||||
|
@ -124,12 +126,9 @@ impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
|
|||
Some(resp) => resp,
|
||||
None => return None,
|
||||
};
|
||||
Some((
|
||||
ReleaseHeldHtlc { payment_release_secret: message.payment_release_secret },
|
||||
responder.respond(),
|
||||
))
|
||||
Some((ReleaseHeldHtlc {}, responder.respond()))
|
||||
}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc) {}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
@ -280,6 +280,10 @@ pub enum MessageContext {
|
|||
///
|
||||
/// [`OffersMessage`]: crate::onion_message::offers::OffersMessage
|
||||
Offers(OffersContext),
|
||||
/// Context specific to an [`AsyncPaymentsMessage`].
|
||||
///
|
||||
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
|
||||
AsyncPayments(AsyncPaymentsContext),
|
||||
/// Context specific to a [`CustomOnionMessageHandler::CustomMessage`].
|
||||
///
|
||||
/// [`CustomOnionMessageHandler::CustomMessage`]: crate::onion_message::messenger::CustomOnionMessageHandler::CustomMessage
|
||||
|
@ -363,9 +367,41 @@ pub enum OffersContext {
|
|||
},
|
||||
}
|
||||
|
||||
/// Contains data specific to an [`AsyncPaymentsMessage`].
|
||||
///
|
||||
/// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AsyncPaymentsContext {
|
||||
/// Context contained within the reply [`BlindedMessagePath`] we put in outbound
|
||||
/// [`HeldHtlcAvailable`] messages, provided back to us in corresponding [`ReleaseHeldHtlc`]
|
||||
/// messages.
|
||||
///
|
||||
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
|
||||
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
|
||||
OutboundPayment {
|
||||
/// ID used when payment to the originating [`Offer`] was initiated. Useful for us to identify
|
||||
/// which of our pending outbound payments should be released to its often-offline payee.
|
||||
///
|
||||
/// [`Offer`]: crate::offers::offer::Offer
|
||||
payment_id: PaymentId,
|
||||
/// A nonce used for authenticating that a [`ReleaseHeldHtlc`] message is valid for a preceding
|
||||
/// [`HeldHtlcAvailable`] message.
|
||||
///
|
||||
/// [`ReleaseHeldHtlc`]: crate::onion_message::async_payments::ReleaseHeldHtlc
|
||||
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
|
||||
nonce: Nonce,
|
||||
/// Authentication code for the [`PaymentId`].
|
||||
///
|
||||
/// Prevents the recipient from being able to deanonymize us by creating a blinded path to us
|
||||
/// containing the expected [`PaymentId`].
|
||||
hmac: Hmac<Sha256>,
|
||||
},
|
||||
}
|
||||
|
||||
impl_writeable_tlv_based_enum!(MessageContext,
|
||||
{0, Offers} => (),
|
||||
{1, Custom} => (),
|
||||
{2, AsyncPayments} => (),
|
||||
);
|
||||
|
||||
impl_writeable_tlv_based_enum!(OffersContext,
|
||||
|
@ -384,6 +420,14 @@ impl_writeable_tlv_based_enum!(OffersContext,
|
|||
},
|
||||
);
|
||||
|
||||
impl_writeable_tlv_based_enum!(AsyncPaymentsContext,
|
||||
(0, OutboundPayment) => {
|
||||
(0, payment_id, required),
|
||||
(2, nonce, required),
|
||||
(4, hmac, required),
|
||||
},
|
||||
);
|
||||
|
||||
/// Construct blinded onion message hops for the given `intermediate_nodes` and `recipient_node_id`.
|
||||
pub(super) fn blinded_hops<T: secp256k1::Signing + secp256k1::Verification>(
|
||||
secp_ctx: &Secp256k1<T>, intermediate_nodes: &[MessageForwardNode],
|
||||
|
|
|
@ -33,7 +33,7 @@ use bitcoin::secp256k1::Secp256k1;
|
|||
use bitcoin::{secp256k1, Sequence};
|
||||
|
||||
use crate::events::FundingInfo;
|
||||
use crate::blinded_path::message::{MessageContext, OffersContext};
|
||||
use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext};
|
||||
use crate::blinded_path::NodeIdLookUp;
|
||||
use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode};
|
||||
use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, ReceiveTlvs};
|
||||
|
@ -71,6 +71,8 @@ use crate::offers::offer::{Offer, OfferBuilder};
|
|||
use crate::offers::parse::Bolt12SemanticError;
|
||||
use crate::offers::refund::{Refund, RefundBuilder};
|
||||
use crate::offers::signer;
|
||||
#[cfg(async_payments)]
|
||||
use crate::offers::static_invoice::StaticInvoice;
|
||||
use crate::onion_message::async_payments::{AsyncPaymentsMessage, HeldHtlcAvailable, ReleaseHeldHtlc, AsyncPaymentsMessageHandler};
|
||||
use crate::onion_message::messenger::{Destination, MessageRouter, Responder, ResponseInstruction, MessageSendInstructions};
|
||||
use crate::onion_message::offers::{OffersMessage, OffersMessageHandler};
|
||||
|
@ -418,7 +420,7 @@ pub trait Verification {
|
|||
) -> Hmac<Sha256>;
|
||||
|
||||
/// Authenticates the data using an HMAC and a [`Nonce`] taken from an [`OffersContext`].
|
||||
fn verify(
|
||||
fn verify_for_offer_payment(
|
||||
&self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
|
||||
) -> Result<(), ()>;
|
||||
}
|
||||
|
@ -434,7 +436,7 @@ impl Verification for PaymentHash {
|
|||
|
||||
/// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an
|
||||
/// [`OffersContext::InboundPayment`].
|
||||
fn verify(
|
||||
fn verify_for_offer_payment(
|
||||
&self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
|
||||
) -> Result<(), ()> {
|
||||
signer::verify_payment_hash(*self, hmac, nonce, expanded_key)
|
||||
|
@ -451,6 +453,24 @@ pub struct PaymentId(pub [u8; Self::LENGTH]);
|
|||
impl PaymentId {
|
||||
/// Number of bytes in the id.
|
||||
pub const LENGTH: usize = 32;
|
||||
|
||||
/// Constructs an HMAC to include in [`AsyncPaymentsContext::OutboundPayment`] for the payment id
|
||||
/// along with the given [`Nonce`].
|
||||
#[cfg(async_payments)]
|
||||
pub fn hmac_for_async_payment(
|
||||
&self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
|
||||
) -> Hmac<Sha256> {
|
||||
signer::hmac_for_async_payment_id(*self, nonce, expanded_key)
|
||||
}
|
||||
|
||||
/// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an
|
||||
/// [`AsyncPaymentsContext::OutboundPayment`].
|
||||
#[cfg(async_payments)]
|
||||
pub fn verify_for_async_payment(
|
||||
&self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
|
||||
) -> Result<(), ()> {
|
||||
signer::verify_async_payment_id(*self, hmac, nonce, expanded_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Verification for PaymentId {
|
||||
|
@ -459,15 +479,15 @@ impl Verification for PaymentId {
|
|||
fn hmac_for_offer_payment(
|
||||
&self, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
|
||||
) -> Hmac<Sha256> {
|
||||
signer::hmac_for_payment_id(*self, nonce, expanded_key)
|
||||
signer::hmac_for_offer_payment_id(*self, nonce, expanded_key)
|
||||
}
|
||||
|
||||
/// Authenticates the payment id using an HMAC and a [`Nonce`] taken from an
|
||||
/// [`OffersContext::OutboundPayment`].
|
||||
fn verify(
|
||||
fn verify_for_offer_payment(
|
||||
&self, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &inbound_payment::ExpandedKey,
|
||||
) -> Result<(), ()> {
|
||||
signer::verify_payment_id(*self, hmac, nonce, expanded_key)
|
||||
signer::verify_offer_payment_id(*self, hmac, nonce, expanded_key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2060,6 +2080,8 @@ where
|
|||
//
|
||||
// `pending_offers_messages`
|
||||
//
|
||||
// `pending_async_payments_messages`
|
||||
//
|
||||
// `total_consistency_lock`
|
||||
// |
|
||||
// |__`forward_htlcs`
|
||||
|
@ -2315,6 +2337,7 @@ where
|
|||
pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
|
||||
#[cfg(any(test, feature = "_test_utils"))]
|
||||
pub(crate) pending_offers_messages: Mutex<Vec<(OffersMessage, MessageSendInstructions)>>,
|
||||
pending_async_payments_messages: Mutex<Vec<(AsyncPaymentsMessage, MessageSendInstructions)>>,
|
||||
|
||||
/// Tracks the message events that are to be broadcasted when we are connected to some peer.
|
||||
pending_broadcast_messages: Mutex<Vec<MessageSendEvent>>,
|
||||
|
@ -2543,14 +2566,17 @@ pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 6
|
|||
pub enum RecentPaymentDetails {
|
||||
/// When an invoice was requested and thus a payment has not yet been sent.
|
||||
AwaitingInvoice {
|
||||
/// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
|
||||
/// a payment and ensure idempotency in LDK.
|
||||
/// A user-provided identifier in [`ChannelManager::pay_for_offer`] used to uniquely identify a
|
||||
/// payment and ensure idempotency in LDK.
|
||||
payment_id: PaymentId,
|
||||
},
|
||||
/// When a payment is still being sent and awaiting successful delivery.
|
||||
Pending {
|
||||
/// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
|
||||
/// a payment and ensure idempotency in LDK.
|
||||
/// A user-provided identifier in [`send_payment`] or [`pay_for_offer`] used to uniquely
|
||||
/// identify a payment and ensure idempotency in LDK.
|
||||
///
|
||||
/// [`send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
|
||||
/// [`pay_for_offer`]: crate::ln::channelmanager::ChannelManager::pay_for_offer
|
||||
payment_id: PaymentId,
|
||||
/// Hash of the payment that is currently being sent but has yet to be fulfilled or
|
||||
/// abandoned.
|
||||
|
@ -2563,8 +2589,11 @@ pub enum RecentPaymentDetails {
|
|||
/// been resolved. Upon receiving [`Event::PaymentSent`], we delay for a few minutes before the
|
||||
/// payment is removed from tracking.
|
||||
Fulfilled {
|
||||
/// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
|
||||
/// a payment and ensure idempotency in LDK.
|
||||
/// A user-provided identifier in [`send_payment`] or [`pay_for_offer`] used to uniquely
|
||||
/// identify a payment and ensure idempotency in LDK.
|
||||
///
|
||||
/// [`send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
|
||||
/// [`pay_for_offer`]: crate::ln::channelmanager::ChannelManager::pay_for_offer
|
||||
payment_id: PaymentId,
|
||||
/// Hash of the payment that was claimed. `None` for serializations of [`ChannelManager`]
|
||||
/// made before LDK version 0.0.104.
|
||||
|
@ -2574,8 +2603,11 @@ pub enum RecentPaymentDetails {
|
|||
/// abandoned via [`ChannelManager::abandon_payment`], it is marked as abandoned until all
|
||||
/// pending HTLCs for this payment resolve and an [`Event::PaymentFailed`] is generated.
|
||||
Abandoned {
|
||||
/// A user-provided identifier in [`ChannelManager::send_payment`] used to uniquely identify
|
||||
/// a payment and ensure idempotency in LDK.
|
||||
/// A user-provided identifier in [`send_payment`] or [`pay_for_offer`] used to uniquely
|
||||
/// identify a payment and ensure idempotency in LDK.
|
||||
///
|
||||
/// [`send_payment`]: crate::ln::channelmanager::ChannelManager::send_payment
|
||||
/// [`pay_for_offer`]: crate::ln::channelmanager::ChannelManager::pay_for_offer
|
||||
payment_id: PaymentId,
|
||||
/// Hash of the payment that we have given up trying to send.
|
||||
payment_hash: PaymentHash,
|
||||
|
@ -3135,6 +3167,7 @@ where
|
|||
funding_batch_states: Mutex::new(BTreeMap::new()),
|
||||
|
||||
pending_offers_messages: Mutex::new(Vec::new()),
|
||||
pending_async_payments_messages: Mutex::new(Vec::new()),
|
||||
pending_broadcast_messages: Mutex::new(Vec::new()),
|
||||
|
||||
last_days_feerates: Mutex::new(VecDeque::new()),
|
||||
|
@ -3370,6 +3403,9 @@ where
|
|||
PendingOutboundPayment::InvoiceReceived { .. } => {
|
||||
Some(RecentPaymentDetails::AwaitingInvoice { payment_id: *payment_id })
|
||||
},
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => {
|
||||
Some(RecentPaymentDetails::AwaitingInvoice { payment_id: *payment_id })
|
||||
},
|
||||
PendingOutboundPayment::Retryable { payment_hash, total_msat, .. } => {
|
||||
Some(RecentPaymentDetails::Pending {
|
||||
payment_id: *payment_id,
|
||||
|
@ -4311,6 +4347,92 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
fn initiate_async_payment(
|
||||
&self, invoice: &StaticInvoice, payment_id: PaymentId
|
||||
) -> Result<(), Bolt12PaymentError> {
|
||||
let mut res = Ok(());
|
||||
PersistenceNotifierGuard::optionally_notify(self, || {
|
||||
let best_block_height = self.best_block.read().unwrap().height;
|
||||
let features = self.bolt12_invoice_features();
|
||||
let outbound_pmts_res = self.pending_outbound_payments.static_invoice_received(
|
||||
invoice, payment_id, features, best_block_height, &*self.entropy_source,
|
||||
&self.pending_events
|
||||
);
|
||||
match outbound_pmts_res {
|
||||
Ok(()) => {},
|
||||
Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) => {
|
||||
res = outbound_pmts_res.map(|_| ());
|
||||
return NotifyOption::SkipPersistNoEvents
|
||||
},
|
||||
Err(e) => {
|
||||
res = Err(e);
|
||||
return NotifyOption::DoPersist
|
||||
}
|
||||
};
|
||||
|
||||
let nonce = Nonce::from_entropy_source(&*self.entropy_source);
|
||||
let hmac = payment_id.hmac_for_async_payment(nonce, &self.inbound_payment_key);
|
||||
let reply_paths = match self.create_blinded_paths(
|
||||
MessageContext::AsyncPayments(
|
||||
AsyncPaymentsContext::OutboundPayment { payment_id, nonce, hmac }
|
||||
)
|
||||
) {
|
||||
Ok(paths) => paths,
|
||||
Err(()) => {
|
||||
self.abandon_payment_with_reason(payment_id, PaymentFailureReason::RouteNotFound);
|
||||
res = Err(Bolt12PaymentError::BlindedPathCreationFailed);
|
||||
return NotifyOption::DoPersist
|
||||
}
|
||||
};
|
||||
|
||||
let mut pending_async_payments_messages = self.pending_async_payments_messages.lock().unwrap();
|
||||
const HTLC_AVAILABLE_LIMIT: usize = 10;
|
||||
reply_paths
|
||||
.iter()
|
||||
.flat_map(|reply_path| invoice.message_paths().iter().map(move |invoice_path| (invoice_path, reply_path)))
|
||||
.take(HTLC_AVAILABLE_LIMIT)
|
||||
.for_each(|(invoice_path, reply_path)| {
|
||||
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
|
||||
destination: Destination::BlindedPath(invoice_path.clone()),
|
||||
reply_path: reply_path.clone(),
|
||||
};
|
||||
let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {});
|
||||
pending_async_payments_messages.push((message, instructions));
|
||||
});
|
||||
|
||||
NotifyOption::DoPersist
|
||||
});
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
fn send_payment_for_static_invoice(
|
||||
&self, payment_id: PaymentId
|
||||
) -> Result<(), Bolt12PaymentError> {
|
||||
let best_block_height = self.best_block.read().unwrap().height;
|
||||
let mut res = Ok(());
|
||||
PersistenceNotifierGuard::optionally_notify(self, || {
|
||||
let outbound_pmts_res = self.pending_outbound_payments.send_payment_for_static_invoice(
|
||||
payment_id, &self.router, self.list_usable_channels(), || self.compute_inflight_htlcs(),
|
||||
&self.entropy_source, &self.node_signer, &self, &self.secp_ctx, best_block_height,
|
||||
&self.logger, &self.pending_events, |args| self.send_payment_along_path(args)
|
||||
);
|
||||
match outbound_pmts_res {
|
||||
Err(Bolt12PaymentError::UnexpectedInvoice) | Err(Bolt12PaymentError::DuplicateInvoice) => {
|
||||
res = outbound_pmts_res.map(|_| ());
|
||||
NotifyOption::SkipPersistNoEvents
|
||||
},
|
||||
other_res => {
|
||||
res = other_res;
|
||||
NotifyOption::DoPersist
|
||||
}
|
||||
}
|
||||
});
|
||||
res
|
||||
}
|
||||
|
||||
/// Signals that no further attempts for the given payment should occur. Useful if you have a
|
||||
/// pending outbound payment with retries remaining, but wish to stop retrying the payment before
|
||||
/// retries are exhausted.
|
||||
|
@ -9124,7 +9246,9 @@ where
|
|||
let invoice_request = builder.build_and_sign()?;
|
||||
|
||||
let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key);
|
||||
let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) };
|
||||
let context = MessageContext::Offers(
|
||||
OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }
|
||||
);
|
||||
let reply_paths = self.create_blinded_paths(context)
|
||||
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
|
||||
|
@ -9244,9 +9368,9 @@ where
|
|||
|
||||
let nonce = Nonce::from_entropy_source(entropy);
|
||||
let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key);
|
||||
let context = OffersContext::InboundPayment {
|
||||
let context = MessageContext::Offers(OffersContext::InboundPayment {
|
||||
payment_hash: invoice.payment_hash(), nonce, hmac
|
||||
};
|
||||
});
|
||||
let reply_paths = self.create_blinded_paths(context)
|
||||
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
|
||||
|
||||
|
@ -9394,7 +9518,7 @@ where
|
|||
if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry {
|
||||
self.create_compact_blinded_paths(context)
|
||||
} else {
|
||||
self.create_blinded_paths(context)
|
||||
self.create_blinded_paths(MessageContext::Offers(context))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9415,7 +9539,7 @@ where
|
|||
/// [`MessageRouter::create_blinded_paths`].
|
||||
///
|
||||
/// Errors if the `MessageRouter` errors.
|
||||
fn create_blinded_paths(&self, context: OffersContext) -> Result<Vec<BlindedMessagePath>, ()> {
|
||||
fn create_blinded_paths(&self, context: MessageContext) -> Result<Vec<BlindedMessagePath>, ()> {
|
||||
let recipient = self.get_our_node_id();
|
||||
let secp_ctx = &self.secp_ctx;
|
||||
|
||||
|
@ -9428,7 +9552,7 @@ where
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
self.router
|
||||
.create_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx)
|
||||
.create_blinded_paths(recipient, context, peers, secp_ctx)
|
||||
.and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(()))
|
||||
}
|
||||
|
||||
|
@ -10832,11 +10956,11 @@ where
|
|||
{
|
||||
let RetryableInvoiceRequest { invoice_request, nonce } = retryable_invoice_request;
|
||||
let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key);
|
||||
let context = OffersContext::OutboundPayment {
|
||||
let context = MessageContext::Offers(OffersContext::OutboundPayment {
|
||||
payment_id,
|
||||
nonce,
|
||||
hmac: Some(hmac)
|
||||
};
|
||||
});
|
||||
match self.create_blinded_paths(context) {
|
||||
Ok(reply_paths) => match self.enqueue_invoice_request(invoice_request, reply_paths) {
|
||||
Ok(_) => {}
|
||||
|
@ -10877,6 +11001,41 @@ where
|
|||
let secp_ctx = &self.secp_ctx;
|
||||
let expanded_key = &self.inbound_payment_key;
|
||||
|
||||
macro_rules! handle_pay_invoice_res {
|
||||
($res: expr, $invoice: expr, $logger: expr) => {{
|
||||
let error = match $res {
|
||||
Err(Bolt12PaymentError::UnknownRequiredFeatures) => {
|
||||
log_trace!(
|
||||
$logger, "Invoice requires unknown features: {:?}",
|
||||
$invoice.invoice_features()
|
||||
);
|
||||
InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures)
|
||||
},
|
||||
Err(Bolt12PaymentError::SendingFailed(e)) => {
|
||||
log_trace!($logger, "Failed paying invoice: {:?}", e);
|
||||
InvoiceError::from_string(format!("{:?}", e))
|
||||
},
|
||||
#[cfg(async_payments)]
|
||||
Err(Bolt12PaymentError::BlindedPathCreationFailed) => {
|
||||
let err_msg = "Failed to create a blinded path back to ourselves";
|
||||
log_trace!($logger, "{}", err_msg);
|
||||
InvoiceError::from_string(err_msg.to_string())
|
||||
},
|
||||
Err(Bolt12PaymentError::UnexpectedInvoice)
|
||||
| Err(Bolt12PaymentError::DuplicateInvoice)
|
||||
| Ok(()) => return None,
|
||||
};
|
||||
|
||||
match responder {
|
||||
Some(responder) => return Some((OffersMessage::InvoiceError(error), responder.respond())),
|
||||
None => {
|
||||
log_trace!($logger, "No reply path to send error: {:?}", error);
|
||||
return None
|
||||
},
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
match message {
|
||||
OffersMessage::InvoiceRequest(invoice_request) => {
|
||||
let responder = match responder {
|
||||
|
@ -11003,48 +11162,27 @@ where
|
|||
return None;
|
||||
}
|
||||
|
||||
let error = match self.send_payment_for_verified_bolt12_invoice(
|
||||
&invoice, payment_id,
|
||||
) {
|
||||
Err(Bolt12PaymentError::UnknownRequiredFeatures) => {
|
||||
log_trace!(
|
||||
logger, "Invoice requires unknown features: {:?}",
|
||||
invoice.invoice_features()
|
||||
);
|
||||
InvoiceError::from(Bolt12SemanticError::UnknownRequiredFeatures)
|
||||
},
|
||||
Err(Bolt12PaymentError::SendingFailed(e)) => {
|
||||
log_trace!(logger, "Failed paying invoice: {:?}", e);
|
||||
InvoiceError::from_string(format!("{:?}", e))
|
||||
},
|
||||
Err(Bolt12PaymentError::UnexpectedInvoice)
|
||||
| Err(Bolt12PaymentError::DuplicateInvoice)
|
||||
| Ok(()) => return None,
|
||||
};
|
||||
|
||||
match responder {
|
||||
Some(responder) => Some((OffersMessage::InvoiceError(error), responder.respond())),
|
||||
None => {
|
||||
log_trace!(logger, "No reply path to send error: {:?}", error);
|
||||
None
|
||||
},
|
||||
}
|
||||
let res = self.send_payment_for_verified_bolt12_invoice(&invoice, payment_id);
|
||||
handle_pay_invoice_res!(res, invoice, logger);
|
||||
},
|
||||
#[cfg(async_payments)]
|
||||
OffersMessage::StaticInvoice(_invoice) => {
|
||||
match responder {
|
||||
Some(responder) => {
|
||||
return Some((OffersMessage::InvoiceError(
|
||||
InvoiceError::from_string("Static invoices not yet supported".to_string())
|
||||
), responder.respond()));
|
||||
OffersMessage::StaticInvoice(invoice) => {
|
||||
let payment_id = match context {
|
||||
Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => {
|
||||
if payment_id.verify_for_offer_payment(hmac, nonce, expanded_key).is_err() {
|
||||
return None
|
||||
}
|
||||
payment_id
|
||||
},
|
||||
None => return None,
|
||||
}
|
||||
_ => return None
|
||||
};
|
||||
let res = self.initiate_async_payment(&invoice, payment_id);
|
||||
handle_pay_invoice_res!(res, invoice, self.logger);
|
||||
},
|
||||
OffersMessage::InvoiceError(invoice_error) => {
|
||||
let payment_hash = match context {
|
||||
Some(OffersContext::InboundPayment { payment_hash, nonce, hmac }) => {
|
||||
match payment_hash.verify(hmac, nonce, expanded_key) {
|
||||
match payment_hash.verify_for_offer_payment(hmac, nonce, expanded_key) {
|
||||
Ok(_) => Some(payment_hash),
|
||||
Err(_) => None,
|
||||
}
|
||||
|
@ -11057,7 +11195,7 @@ where
|
|||
|
||||
match context {
|
||||
Some(OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) }) => {
|
||||
if let Ok(()) = payment_id.verify(hmac, nonce, expanded_key) {
|
||||
if let Ok(()) = payment_id.verify_for_offer_payment(hmac, nonce, expanded_key) {
|
||||
self.abandon_payment_with_reason(
|
||||
payment_id, PaymentFailureReason::InvoiceRequestRejected,
|
||||
);
|
||||
|
@ -11094,10 +11232,20 @@ where
|
|||
None
|
||||
}
|
||||
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc) {}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {
|
||||
#[cfg(async_payments)] {
|
||||
let AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } = _context;
|
||||
if payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key).is_err() { return }
|
||||
if let Err(e) = self.send_payment_for_static_invoice(payment_id) {
|
||||
log_trace!(
|
||||
self.logger, "Failed to release held HTLC with payment id {}: {:?}", payment_id, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn release_pending_messages(&self) -> Vec<(AsyncPaymentsMessage, MessageSendInstructions)> {
|
||||
Vec::new()
|
||||
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11718,6 +11866,7 @@ where
|
|||
}
|
||||
PendingOutboundPayment::AwaitingInvoice { .. } => {},
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => {},
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => {},
|
||||
PendingOutboundPayment::Fulfilled { .. } => {},
|
||||
PendingOutboundPayment::Abandoned { .. } => {},
|
||||
}
|
||||
|
@ -12827,6 +12976,7 @@ where
|
|||
funding_batch_states: Mutex::new(BTreeMap::new()),
|
||||
|
||||
pending_offers_messages: Mutex::new(Vec::new()),
|
||||
pending_async_payments_messages: Mutex::new(Vec::new()),
|
||||
|
||||
pending_broadcast_messages: Mutex::new(Vec::new()),
|
||||
|
||||
|
|
|
@ -32,6 +32,12 @@ use crate::util::logger::Logger;
|
|||
use crate::util::time::Instant;
|
||||
use crate::util::ser::ReadableArgs;
|
||||
|
||||
#[cfg(async_payments)]
|
||||
use {
|
||||
crate::offers::invoice::{DerivedSigningPubkey, InvoiceBuilder},
|
||||
crate::offers::static_invoice::StaticInvoice,
|
||||
};
|
||||
|
||||
use core::fmt::{self, Display, Formatter};
|
||||
use core::ops::Deref;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
@ -58,6 +64,9 @@ pub(crate) enum PendingOutboundPayment {
|
|||
max_total_routing_fee_msat: Option<u64>,
|
||||
retryable_invoice_request: Option<RetryableInvoiceRequest>
|
||||
},
|
||||
// This state will never be persisted to disk because we transition from `AwaitingInvoice` to
|
||||
// `Retryable` atomically within the `ChannelManager::total_consistency_lock`. Useful to avoid
|
||||
// holding the `OutboundPayments::pending_outbound_payments` lock during pathfinding.
|
||||
InvoiceReceived {
|
||||
payment_hash: PaymentHash,
|
||||
retry_strategy: Retry,
|
||||
|
@ -65,6 +74,16 @@ pub(crate) enum PendingOutboundPayment {
|
|||
// used anywhere.
|
||||
max_total_routing_fee_msat: Option<u64>,
|
||||
},
|
||||
// This state applies when we are paying an often-offline recipient and another node on the
|
||||
// network served us a static invoice on the recipient's behalf in response to our invoice
|
||||
// request. As a result, once a payment gets in this state it will remain here until the recipient
|
||||
// comes back online, which may take hours or even days.
|
||||
StaticInvoiceReceived {
|
||||
payment_hash: PaymentHash,
|
||||
keysend_preimage: PaymentPreimage,
|
||||
retry_strategy: Retry,
|
||||
route_params: RouteParameters,
|
||||
},
|
||||
Retryable {
|
||||
retry_strategy: Option<Retry>,
|
||||
attempts: PaymentAttempts,
|
||||
|
@ -182,6 +201,7 @@ impl PendingOutboundPayment {
|
|||
PendingOutboundPayment::Legacy { .. } => None,
|
||||
PendingOutboundPayment::AwaitingInvoice { .. } => None,
|
||||
PendingOutboundPayment::InvoiceReceived { payment_hash, .. } => Some(*payment_hash),
|
||||
PendingOutboundPayment::StaticInvoiceReceived { payment_hash, .. } => Some(*payment_hash),
|
||||
PendingOutboundPayment::Retryable { payment_hash, .. } => Some(*payment_hash),
|
||||
PendingOutboundPayment::Fulfilled { payment_hash, .. } => *payment_hash,
|
||||
PendingOutboundPayment::Abandoned { payment_hash, .. } => Some(*payment_hash),
|
||||
|
@ -196,27 +216,34 @@ impl PendingOutboundPayment {
|
|||
PendingOutboundPayment::Fulfilled { session_privs, .. } |
|
||||
PendingOutboundPayment::Abandoned { session_privs, .. } => session_privs,
|
||||
PendingOutboundPayment::AwaitingInvoice { .. } |
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); return; },
|
||||
PendingOutboundPayment::InvoiceReceived { .. } |
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => { debug_assert!(false); return; },
|
||||
});
|
||||
let payment_hash = self.payment_hash();
|
||||
*self = PendingOutboundPayment::Fulfilled { session_privs, payment_hash, timer_ticks_without_htlcs: 0 };
|
||||
}
|
||||
|
||||
fn mark_abandoned(&mut self, reason: PaymentFailureReason) {
|
||||
if let PendingOutboundPayment::Retryable { session_privs, payment_hash, .. } = self {
|
||||
let mut our_session_privs = new_hash_set();
|
||||
core::mem::swap(&mut our_session_privs, session_privs);
|
||||
*self = PendingOutboundPayment::Abandoned {
|
||||
session_privs: our_session_privs,
|
||||
payment_hash: *payment_hash,
|
||||
reason: Some(reason)
|
||||
};
|
||||
} else if let PendingOutboundPayment::InvoiceReceived { payment_hash, .. } = self {
|
||||
*self = PendingOutboundPayment::Abandoned {
|
||||
session_privs: new_hash_set(),
|
||||
payment_hash: *payment_hash,
|
||||
reason: Some(reason)
|
||||
};
|
||||
let session_privs = match self {
|
||||
PendingOutboundPayment::Retryable { session_privs, .. } => {
|
||||
let mut our_session_privs = new_hash_set();
|
||||
core::mem::swap(&mut our_session_privs, session_privs);
|
||||
our_session_privs
|
||||
},
|
||||
_ => new_hash_set(),
|
||||
};
|
||||
match self {
|
||||
Self::Retryable { payment_hash, .. } |
|
||||
Self::InvoiceReceived { payment_hash, .. } |
|
||||
Self::StaticInvoiceReceived { payment_hash, .. } =>
|
||||
{
|
||||
*self = Self::Abandoned {
|
||||
session_privs,
|
||||
payment_hash: *payment_hash,
|
||||
reason: Some(reason),
|
||||
};
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,7 +257,8 @@ impl PendingOutboundPayment {
|
|||
session_privs.remove(session_priv)
|
||||
},
|
||||
PendingOutboundPayment::AwaitingInvoice { .. } |
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); false },
|
||||
PendingOutboundPayment::InvoiceReceived { .. } |
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => { debug_assert!(false); false },
|
||||
};
|
||||
if remove_res {
|
||||
if let PendingOutboundPayment::Retryable {
|
||||
|
@ -259,7 +287,8 @@ impl PendingOutboundPayment {
|
|||
session_privs.insert(session_priv)
|
||||
},
|
||||
PendingOutboundPayment::AwaitingInvoice { .. } |
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => { debug_assert!(false); false },
|
||||
PendingOutboundPayment::InvoiceReceived { .. } |
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => { debug_assert!(false); false },
|
||||
PendingOutboundPayment::Fulfilled { .. } => false,
|
||||
PendingOutboundPayment::Abandoned { .. } => false,
|
||||
};
|
||||
|
@ -292,6 +321,7 @@ impl PendingOutboundPayment {
|
|||
},
|
||||
PendingOutboundPayment::AwaitingInvoice { .. } => 0,
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => 0,
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -511,6 +541,15 @@ pub enum Bolt12PaymentError {
|
|||
UnknownRequiredFeatures,
|
||||
/// The invoice was valid for the corresponding [`PaymentId`], but sending the payment failed.
|
||||
SendingFailed(RetryableSendFailure),
|
||||
#[cfg(async_payments)]
|
||||
/// Failed to create a blinded path back to ourselves.
|
||||
///
|
||||
/// We attempted to initiate payment to a [`StaticInvoice`] but failed to create a reply path for
|
||||
/// our [`HeldHtlcAvailable`] message.
|
||||
///
|
||||
/// [`StaticInvoice`]: crate::offers::static_invoice::StaticInvoice
|
||||
/// [`HeldHtlcAvailable`]: crate::onion_message::async_payments::HeldHtlcAvailable
|
||||
BlindedPathCreationFailed,
|
||||
}
|
||||
|
||||
/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
|
||||
|
@ -811,7 +850,7 @@ impl OutboundPayments {
|
|||
PendingOutboundPayment::AwaitingInvoice {
|
||||
retry_strategy: retry, max_total_routing_fee_msat: max_total_fee, ..
|
||||
} => {
|
||||
retry_strategy = Some(*retry);
|
||||
retry_strategy = *retry;
|
||||
max_total_routing_fee_msat = *max_total_fee;
|
||||
*entry.into_mut() = PendingOutboundPayment::InvoiceReceived {
|
||||
payment_hash,
|
||||
|
@ -831,11 +870,42 @@ impl OutboundPayments {
|
|||
return Err(Bolt12PaymentError::UnknownRequiredFeatures);
|
||||
}
|
||||
|
||||
let mut payment_params = PaymentParameters::from_bolt12_invoice(&invoice);
|
||||
let mut route_params = RouteParameters::from_payment_params_and_value(
|
||||
PaymentParameters::from_bolt12_invoice(&invoice), invoice.amount_msats()
|
||||
);
|
||||
if let Some(max_fee_msat) = max_total_routing_fee_msat {
|
||||
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
|
||||
}
|
||||
self.send_payment_for_bolt12_invoice_internal(
|
||||
payment_id, payment_hash, None, route_params, retry_strategy, router, first_hops,
|
||||
inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx, best_block_height,
|
||||
logger, pending_events, send_payment_along_path
|
||||
)
|
||||
}
|
||||
|
||||
fn send_payment_for_bolt12_invoice_internal<
|
||||
R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref
|
||||
>(
|
||||
&self, payment_id: PaymentId, payment_hash: PaymentHash,
|
||||
keysend_preimage: Option<PaymentPreimage>, mut route_params: RouteParameters,
|
||||
retry_strategy: Retry, router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH,
|
||||
entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL,
|
||||
secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32, logger: &L,
|
||||
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
|
||||
send_payment_along_path: SP,
|
||||
) -> Result<(), Bolt12PaymentError>
|
||||
where
|
||||
R::Target: Router,
|
||||
ES::Target: EntropySource,
|
||||
NS::Target: NodeSigner,
|
||||
NL::Target: NodeIdLookUp,
|
||||
L::Target: Logger,
|
||||
IH: Fn() -> InFlightHtlcs,
|
||||
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
||||
{
|
||||
// Advance any blinded path where the introduction node is our node.
|
||||
if let Ok(our_node_id) = node_signer.get_node_id(Recipient::Node) {
|
||||
for path in payment_params.payee.blinded_route_hints_mut().iter_mut() {
|
||||
for path in route_params.payment_params.payee.blinded_route_hints_mut().iter_mut() {
|
||||
let introduction_node_id = match path.introduction_node() {
|
||||
IntroductionNode::NodeId(pubkey) => *pubkey,
|
||||
IntroductionNode::DirectedShortChannelId(direction, scid) => {
|
||||
|
@ -851,15 +921,6 @@ impl OutboundPayments {
|
|||
}
|
||||
}
|
||||
|
||||
let amount_msat = invoice.amount_msats();
|
||||
let mut route_params = RouteParameters::from_payment_params_and_value(
|
||||
payment_params, amount_msat
|
||||
);
|
||||
|
||||
if let Some(max_fee_msat) = max_total_routing_fee_msat {
|
||||
route_params.max_total_routing_fee_msat = Some(max_fee_msat);
|
||||
}
|
||||
|
||||
let recipient_onion = RecipientOnionFields {
|
||||
payment_secret: None,
|
||||
payment_metadata: None,
|
||||
|
@ -884,12 +945,13 @@ impl OutboundPayments {
|
|||
|
||||
let payment_params = Some(route_params.payment_params.clone());
|
||||
let (retryable_payment, onion_session_privs) = self.create_pending_payment(
|
||||
payment_hash, recipient_onion.clone(), None, &route,
|
||||
retry_strategy, payment_params, entropy_source, best_block_height
|
||||
payment_hash, recipient_onion.clone(), keysend_preimage, &route, Some(retry_strategy),
|
||||
payment_params, entropy_source, best_block_height
|
||||
);
|
||||
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
|
||||
hash_map::Entry::Occupied(entry) => match entry.get() {
|
||||
PendingOutboundPayment::InvoiceReceived { .. } => {
|
||||
PendingOutboundPayment::InvoiceReceived { .. }
|
||||
| PendingOutboundPayment::StaticInvoiceReceived { .. } => {
|
||||
*entry.into_mut() = retryable_payment;
|
||||
},
|
||||
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
|
||||
|
@ -898,7 +960,7 @@ impl OutboundPayments {
|
|||
}
|
||||
|
||||
let result = self.pay_route_internal(
|
||||
&route, payment_hash, &recipient_onion, None, payment_id,
|
||||
&route, payment_hash, &recipient_onion, keysend_preimage, payment_id,
|
||||
Some(route_params.final_value_msat), onion_session_privs, node_signer,
|
||||
best_block_height, &send_payment_along_path
|
||||
);
|
||||
|
@ -916,6 +978,121 @@ impl OutboundPayments {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
pub(super) fn static_invoice_received<ES: Deref>(
|
||||
&self, invoice: &StaticInvoice, payment_id: PaymentId, features: Bolt12InvoiceFeatures,
|
||||
best_block_height: u32, entropy_source: ES,
|
||||
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>
|
||||
) -> Result<(), Bolt12PaymentError> where ES::Target: EntropySource {
|
||||
macro_rules! abandon_with_entry {
|
||||
($payment: expr, $reason: expr) => {
|
||||
$payment.get_mut().mark_abandoned($reason);
|
||||
if let PendingOutboundPayment::Abandoned { reason, .. } = $payment.get() {
|
||||
if $payment.get().remaining_parts() == 0 {
|
||||
pending_events.lock().unwrap().push_back((events::Event::PaymentFailed {
|
||||
payment_id,
|
||||
payment_hash: None,
|
||||
reason: *reason,
|
||||
}, None));
|
||||
$payment.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
|
||||
hash_map::Entry::Occupied(mut entry) => match entry.get() {
|
||||
PendingOutboundPayment::AwaitingInvoice {
|
||||
retry_strategy, retryable_invoice_request, max_total_routing_fee_msat, ..
|
||||
} => {
|
||||
let invreq = &retryable_invoice_request
|
||||
.as_ref()
|
||||
.ok_or(Bolt12PaymentError::UnexpectedInvoice)?
|
||||
.invoice_request;
|
||||
if !invoice.from_same_offer(invreq) {
|
||||
return Err(Bolt12PaymentError::UnexpectedInvoice)
|
||||
}
|
||||
if invoice.invoice_features().requires_unknown_bits_from(&features) {
|
||||
abandon_with_entry!(entry, PaymentFailureReason::UnknownRequiredFeatures);
|
||||
return Err(Bolt12PaymentError::UnknownRequiredFeatures)
|
||||
}
|
||||
let amount_msat = match InvoiceBuilder::<DerivedSigningPubkey>::amount_msats(invreq) {
|
||||
Ok(amt) => amt,
|
||||
Err(_) => {
|
||||
// We check this during invoice request parsing, when constructing the invreq's
|
||||
// contents from its TLV stream.
|
||||
debug_assert!(false, "LDK requires an msat amount in either the invreq or the invreq's underlying offer");
|
||||
abandon_with_entry!(entry, PaymentFailureReason::UnexpectedError);
|
||||
return Err(Bolt12PaymentError::UnknownRequiredFeatures)
|
||||
}
|
||||
};
|
||||
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);
|
||||
let mut route_params = RouteParameters::from_payment_params_and_value(pay_params, amount_msat);
|
||||
route_params.max_total_routing_fee_msat = *max_total_routing_fee_msat;
|
||||
|
||||
if let Err(()) = onion_utils::set_max_path_length(
|
||||
&mut route_params, &RecipientOnionFields::spontaneous_empty(), Some(keysend_preimage),
|
||||
best_block_height
|
||||
) {
|
||||
abandon_with_entry!(entry, PaymentFailureReason::RouteNotFound);
|
||||
return Err(Bolt12PaymentError::SendingFailed(RetryableSendFailure::OnionPacketSizeExceeded))
|
||||
}
|
||||
|
||||
*entry.into_mut() = PendingOutboundPayment::StaticInvoiceReceived {
|
||||
payment_hash,
|
||||
keysend_preimage,
|
||||
retry_strategy: *retry_strategy,
|
||||
route_params,
|
||||
};
|
||||
return Ok(())
|
||||
},
|
||||
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
|
||||
},
|
||||
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
pub(super) fn send_payment_for_static_invoice<
|
||||
R: Deref, ES: Deref, NS: Deref, NL: Deref, IH, SP, L: Deref
|
||||
>(
|
||||
&self, payment_id: PaymentId, router: &R, first_hops: Vec<ChannelDetails>, inflight_htlcs: IH,
|
||||
entropy_source: &ES, node_signer: &NS, node_id_lookup: &NL,
|
||||
secp_ctx: &Secp256k1<secp256k1::All>, best_block_height: u32, logger: &L,
|
||||
pending_events: &Mutex<VecDeque<(events::Event, Option<EventCompletionAction>)>>,
|
||||
send_payment_along_path: SP,
|
||||
) -> Result<(), Bolt12PaymentError>
|
||||
where
|
||||
R::Target: Router,
|
||||
ES::Target: EntropySource,
|
||||
NS::Target: NodeSigner,
|
||||
NL::Target: NodeIdLookUp,
|
||||
L::Target: Logger,
|
||||
IH: Fn() -> InFlightHtlcs,
|
||||
SP: Fn(SendAlongPathArgs) -> Result<(), APIError>,
|
||||
{
|
||||
let (payment_hash, keysend_preimage, route_params, retry_strategy) =
|
||||
match self.pending_outbound_payments.lock().unwrap().entry(payment_id) {
|
||||
hash_map::Entry::Occupied(entry) => match entry.get() {
|
||||
PendingOutboundPayment::StaticInvoiceReceived {
|
||||
payment_hash, route_params, retry_strategy, keysend_preimage, ..
|
||||
} => {
|
||||
(*payment_hash, *keysend_preimage, route_params.clone(), *retry_strategy)
|
||||
},
|
||||
_ => return Err(Bolt12PaymentError::DuplicateInvoice),
|
||||
},
|
||||
hash_map::Entry::Vacant(_) => return Err(Bolt12PaymentError::UnexpectedInvoice),
|
||||
};
|
||||
|
||||
self.send_payment_for_bolt12_invoice_internal(
|
||||
payment_id, payment_hash, Some(keysend_preimage), route_params, retry_strategy, router,
|
||||
first_hops, inflight_htlcs, entropy_source, node_signer, node_id_lookup, secp_ctx,
|
||||
best_block_height, logger, pending_events, send_payment_along_path
|
||||
)
|
||||
}
|
||||
|
||||
pub(super) fn check_retry_payments<R: Deref, ES: Deref, NS: Deref, SP, IH, FH, L: Deref>(
|
||||
&self, router: &R, first_hops: FH, inflight_htlcs: IH, entropy_source: &ES, node_signer: &NS,
|
||||
best_block_height: u32,
|
||||
|
@ -1195,6 +1372,11 @@ impl OutboundPayments {
|
|||
debug_assert!(false);
|
||||
return
|
||||
},
|
||||
PendingOutboundPayment::StaticInvoiceReceived { .. } => {
|
||||
log_error!(logger, "Payment already initiating");
|
||||
debug_assert!(false);
|
||||
return
|
||||
},
|
||||
PendingOutboundPayment::Fulfilled { .. } => {
|
||||
log_error!(logger, "Payment already completed");
|
||||
return
|
||||
|
@ -1725,6 +1907,22 @@ impl OutboundPayments {
|
|||
true
|
||||
}
|
||||
},
|
||||
PendingOutboundPayment::StaticInvoiceReceived { route_params, payment_hash, .. } => {
|
||||
let is_stale =
|
||||
route_params.payment_params.expiry_time.unwrap_or(u64::MAX) <
|
||||
duration_since_epoch.as_secs();
|
||||
if is_stale {
|
||||
let fail_ev = events::Event::PaymentFailed {
|
||||
payment_id: *payment_id,
|
||||
payment_hash: Some(*payment_hash),
|
||||
reason: Some(PaymentFailureReason::PaymentExpired)
|
||||
};
|
||||
pending_events.push_back((fail_ev, None));
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
},
|
||||
_ => true,
|
||||
});
|
||||
}
|
||||
|
@ -1985,6 +2183,14 @@ impl_writeable_tlv_based_enum_upgradable!(PendingOutboundPayment,
|
|||
(2, retry_strategy, required),
|
||||
(4, max_total_routing_fee_msat, option),
|
||||
},
|
||||
// Added in 0.0.125. Prior versions will drop these outbounds on downgrade, which is safe because
|
||||
// no HTLCs are in-flight.
|
||||
(9, StaticInvoiceReceived) => {
|
||||
(0, payment_hash, required),
|
||||
(2, keysend_preimage, required),
|
||||
(4, retry_strategy, required),
|
||||
(6, route_params, required),
|
||||
},
|
||||
);
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1996,11 +2202,11 @@ mod tests {
|
|||
|
||||
use crate::blinded_path::EmptyNodeIdLookUp;
|
||||
use crate::events::{Event, PathFailure, PaymentFailureReason};
|
||||
use crate::ln::types::PaymentHash;
|
||||
use crate::ln::types::{PaymentHash, PaymentPreimage};
|
||||
use crate::ln::channelmanager::{PaymentId, RecipientOnionFields};
|
||||
use crate::ln::features::{Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
|
||||
use crate::ln::msgs::{ErrorAction, LightningError};
|
||||
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, Retry, RetryableSendFailure, StaleExpiration};
|
||||
use crate::ln::outbound_payment::{Bolt12PaymentError, OutboundPayments, PendingOutboundPayment, Retry, RetryableSendFailure, StaleExpiration};
|
||||
#[cfg(feature = "std")]
|
||||
use crate::offers::invoice::DEFAULT_RELATIVE_EXPIRY;
|
||||
use crate::offers::offer::OfferBuilder;
|
||||
|
@ -2550,4 +2756,89 @@ mod tests {
|
|||
assert!(outbound_payments.has_pending_payments());
|
||||
assert!(pending_events.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn time_out_unreleased_async_payments() {
|
||||
let pending_events = Mutex::new(VecDeque::new());
|
||||
let outbound_payments = OutboundPayments::new(new_hash_map());
|
||||
let payment_id = PaymentId([0; 32]);
|
||||
let absolute_expiry = 60;
|
||||
|
||||
let mut outbounds = outbound_payments.pending_outbound_payments.lock().unwrap();
|
||||
let payment_params = PaymentParameters::from_node_id(test_utils::pubkey(42), 0)
|
||||
.with_expiry_time(absolute_expiry);
|
||||
let route_params = RouteParameters {
|
||||
payment_params,
|
||||
final_value_msat: 0,
|
||||
max_total_routing_fee_msat: None,
|
||||
};
|
||||
let payment_hash = PaymentHash([0; 32]);
|
||||
let outbound = PendingOutboundPayment::StaticInvoiceReceived {
|
||||
payment_hash,
|
||||
keysend_preimage: PaymentPreimage([0; 32]),
|
||||
retry_strategy: Retry::Attempts(0),
|
||||
route_params,
|
||||
};
|
||||
outbounds.insert(payment_id, outbound);
|
||||
core::mem::drop(outbounds);
|
||||
|
||||
// The payment will not be removed if it isn't expired yet.
|
||||
outbound_payments.remove_stale_payments(Duration::from_secs(absolute_expiry), &pending_events);
|
||||
let outbounds = outbound_payments.pending_outbound_payments.lock().unwrap();
|
||||
assert_eq!(outbounds.len(), 1);
|
||||
let events = pending_events.lock().unwrap();
|
||||
assert_eq!(events.len(), 0);
|
||||
core::mem::drop(outbounds);
|
||||
core::mem::drop(events);
|
||||
|
||||
outbound_payments.remove_stale_payments(Duration::from_secs(absolute_expiry + 1), &pending_events);
|
||||
let outbounds = outbound_payments.pending_outbound_payments.lock().unwrap();
|
||||
assert_eq!(outbounds.len(), 0);
|
||||
let events = pending_events.lock().unwrap();
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(events[0], (Event::PaymentFailed {
|
||||
payment_hash: Some(payment_hash),
|
||||
payment_id,
|
||||
reason: Some(PaymentFailureReason::PaymentExpired),
|
||||
}, None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abandon_unreleased_async_payment() {
|
||||
let pending_events = Mutex::new(VecDeque::new());
|
||||
let outbound_payments = OutboundPayments::new(new_hash_map());
|
||||
let payment_id = PaymentId([0; 32]);
|
||||
let absolute_expiry = 60;
|
||||
|
||||
let mut outbounds = outbound_payments.pending_outbound_payments.lock().unwrap();
|
||||
let payment_params = PaymentParameters::from_node_id(test_utils::pubkey(42), 0)
|
||||
.with_expiry_time(absolute_expiry);
|
||||
let route_params = RouteParameters {
|
||||
payment_params,
|
||||
final_value_msat: 0,
|
||||
max_total_routing_fee_msat: None,
|
||||
};
|
||||
let payment_hash = PaymentHash([0; 32]);
|
||||
let outbound = PendingOutboundPayment::StaticInvoiceReceived {
|
||||
payment_hash,
|
||||
keysend_preimage: PaymentPreimage([0; 32]),
|
||||
retry_strategy: Retry::Attempts(0),
|
||||
route_params,
|
||||
};
|
||||
outbounds.insert(payment_id, outbound);
|
||||
core::mem::drop(outbounds);
|
||||
|
||||
outbound_payments.abandon_payment(
|
||||
payment_id, PaymentFailureReason::UserAbandoned, &pending_events
|
||||
);
|
||||
let outbounds = outbound_payments.pending_outbound_payments.lock().unwrap();
|
||||
assert_eq!(outbounds.len(), 0);
|
||||
let events = pending_events.lock().unwrap();
|
||||
assert_eq!(events.len(), 1);
|
||||
assert_eq!(events[0], (Event::PaymentFailed {
|
||||
payment_hash: Some(payment_hash),
|
||||
payment_id,
|
||||
reason: Some(PaymentFailureReason::UserAbandoned),
|
||||
}, None));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
use bitcoin::constants::ChainHash;
|
||||
use bitcoin::secp256k1::{self, Secp256k1, SecretKey, PublicKey};
|
||||
|
||||
use crate::blinded_path::message::OffersContext;
|
||||
use crate::blinded_path::message::{AsyncPaymentsContext, OffersContext};
|
||||
use crate::sign::{NodeSigner, Recipient};
|
||||
use crate::events::{MessageSendEvent, MessageSendEventsProvider};
|
||||
use crate::ln::types::ChannelId;
|
||||
|
@ -152,7 +152,7 @@ impl AsyncPaymentsMessageHandler for IgnoringMessageHandler {
|
|||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
|
||||
None
|
||||
}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc) {}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
}
|
||||
impl CustomOnionMessageHandler for IgnoringMessageHandler {
|
||||
type CustomMessage = Infallible;
|
||||
|
|
|
@ -840,6 +840,11 @@ impl InvoiceRequest {
|
|||
invoice_request_accessors!(self, self.contents);
|
||||
invoice_request_respond_with_explicit_signing_pubkey_methods!(self, self, InvoiceBuilder<ExplicitSigningPubkey>);
|
||||
invoice_request_verify_method!(self, Self);
|
||||
|
||||
#[cfg(async_payments)]
|
||||
pub(super) fn bytes(&self) -> &Vec<u8> {
|
||||
&self.bytes
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(c_bindings)]
|
||||
|
|
|
@ -249,6 +249,7 @@ impl<'a> TlvStream<'a> {
|
|||
}
|
||||
|
||||
/// A slice into a [`TlvStream`] for a record.
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub(super) struct TlvRecord<'a> {
|
||||
pub(super) r#type: u64,
|
||||
type_bytes: &'a [u8],
|
||||
|
|
|
@ -38,10 +38,13 @@ const WITHOUT_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[3; 16];
|
|||
const WITH_ENCRYPTED_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[4; 16];
|
||||
|
||||
// HMAC input for a `PaymentId`. The HMAC is used in `OffersContext::OutboundPayment`.
|
||||
const PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16];
|
||||
const OFFER_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[5; 16];
|
||||
// HMAC input for a `PaymentId`. The HMAC is used in `AsyncPaymentsContext::OutboundPayment`.
|
||||
#[cfg(async_payments)]
|
||||
const ASYNC_PAYMENT_ID_HMAC_INPUT: &[u8; 16] = &[6; 16];
|
||||
|
||||
// HMAC input for a `PaymentHash`. The HMAC is used in `OffersContext::InboundPayment`.
|
||||
const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[6; 16];
|
||||
const PAYMENT_HASH_HMAC_INPUT: &[u8; 16] = &[7; 16];
|
||||
|
||||
/// Message metadata which possibly is derived from [`MetadataMaterial`] such that it can be
|
||||
/// verified.
|
||||
|
@ -399,23 +402,16 @@ fn hmac_for_message<'a>(
|
|||
Ok(hmac)
|
||||
}
|
||||
|
||||
pub(crate) fn hmac_for_payment_id(
|
||||
pub(crate) fn hmac_for_offer_payment_id(
|
||||
payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey,
|
||||
) -> Hmac<Sha256> {
|
||||
const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~";
|
||||
let mut hmac = expanded_key.hmac_for_offer();
|
||||
hmac.input(IV_BYTES);
|
||||
hmac.input(&nonce.0);
|
||||
hmac.input(PAYMENT_ID_HMAC_INPUT);
|
||||
hmac.input(&payment_id.0);
|
||||
|
||||
Hmac::from_engine(hmac)
|
||||
hmac_for_payment_id(payment_id, nonce, OFFER_PAYMENT_ID_HMAC_INPUT, expanded_key)
|
||||
}
|
||||
|
||||
pub(crate) fn verify_payment_id(
|
||||
pub(crate) fn verify_offer_payment_id(
|
||||
payment_id: PaymentId, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &ExpandedKey,
|
||||
) -> Result<(), ()> {
|
||||
if hmac_for_payment_id(payment_id, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
|
||||
if hmac_for_offer_payment_id(payment_id, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
|
||||
}
|
||||
|
||||
pub(crate) fn hmac_for_payment_hash(
|
||||
|
@ -436,3 +432,30 @@ pub(crate) fn verify_payment_hash(
|
|||
) -> Result<(), ()> {
|
||||
if hmac_for_payment_hash(payment_hash, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
pub(crate) fn hmac_for_async_payment_id(
|
||||
payment_id: PaymentId, nonce: Nonce, expanded_key: &ExpandedKey,
|
||||
) -> Hmac<Sha256> {
|
||||
hmac_for_payment_id(payment_id, nonce, ASYNC_PAYMENT_ID_HMAC_INPUT, expanded_key)
|
||||
}
|
||||
|
||||
#[cfg(async_payments)]
|
||||
pub(crate) fn verify_async_payment_id(
|
||||
payment_id: PaymentId, hmac: Hmac<Sha256>, nonce: Nonce, expanded_key: &ExpandedKey,
|
||||
) -> Result<(), ()> {
|
||||
if hmac_for_async_payment_id(payment_id, nonce, expanded_key) == hmac { Ok(()) } else { Err(()) }
|
||||
}
|
||||
|
||||
fn hmac_for_payment_id(
|
||||
payment_id: PaymentId, nonce: Nonce, hmac_input: &[u8; 16], expanded_key: &ExpandedKey,
|
||||
) -> Hmac<Sha256> {
|
||||
const IV_BYTES: &[u8; IV_LEN] = b"LDK Payment ID ~";
|
||||
let mut hmac = expanded_key.hmac_for_offer();
|
||||
hmac.input(IV_BYTES);
|
||||
hmac.input(&nonce.0);
|
||||
hmac.input(hmac_input);
|
||||
hmac.input(&payment_id.0);
|
||||
|
||||
Hmac::from_engine(hmac)
|
||||
}
|
||||
|
|
|
@ -20,12 +20,13 @@ use crate::offers::invoice::{
|
|||
InvoiceTlvStream, InvoiceTlvStreamRef,
|
||||
};
|
||||
use crate::offers::invoice_macros::{invoice_accessors_common, invoice_builder_methods_common};
|
||||
use crate::offers::invoice_request::InvoiceRequest;
|
||||
use crate::offers::merkle::{
|
||||
self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash,
|
||||
self, SignError, SignFn, SignatureTlvStream, SignatureTlvStreamRef, TaggedHash, TlvStream,
|
||||
};
|
||||
use crate::offers::nonce::Nonce;
|
||||
use crate::offers::offer::{
|
||||
Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef, Quantity,
|
||||
Amount, Offer, OfferContents, OfferTlvStream, OfferTlvStreamRef, Quantity, OFFER_TYPES,
|
||||
};
|
||||
use crate::offers::parse::{Bolt12ParseError, Bolt12SemanticError, ParsedMessage};
|
||||
use crate::util::ser::{CursorReadable, Iterable, WithoutLength, Writeable, Writer};
|
||||
|
@ -312,6 +313,16 @@ impl StaticInvoice {
|
|||
pub fn signature(&self) -> Signature {
|
||||
self.signature
|
||||
}
|
||||
|
||||
pub(crate) fn from_same_offer(&self, invreq: &InvoiceRequest) -> bool {
|
||||
let invoice_offer_tlv_stream = TlvStream::new(&self.bytes)
|
||||
.range(OFFER_TYPES)
|
||||
.map(|tlv_record| tlv_record.record_bytes);
|
||||
let invreq_offer_tlv_stream = TlvStream::new(invreq.bytes())
|
||||
.range(OFFER_TYPES)
|
||||
.map(|tlv_record| tlv_record.record_bytes);
|
||||
invoice_offer_tlv_stream.eq(invreq_offer_tlv_stream)
|
||||
}
|
||||
}
|
||||
|
||||
impl InvoiceContents {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
//! Message handling for async payments.
|
||||
|
||||
use crate::blinded_path::message::AsyncPaymentsContext;
|
||||
use crate::io;
|
||||
use crate::ln::msgs::DecodeError;
|
||||
use crate::onion_message::messenger::{MessageSendInstructions, Responder, ResponseInstruction};
|
||||
|
@ -32,7 +33,7 @@ pub trait AsyncPaymentsMessageHandler {
|
|||
|
||||
/// Handle a [`ReleaseHeldHtlc`] message. If authentication of the message succeeds, an HTLC
|
||||
/// should be released to the corresponding payee.
|
||||
fn release_held_htlc(&self, message: ReleaseHeldHtlc);
|
||||
fn release_held_htlc(&self, message: ReleaseHeldHtlc, context: AsyncPaymentsContext);
|
||||
|
||||
/// Release any [`AsyncPaymentsMessage`]s that need to be sent.
|
||||
///
|
||||
|
@ -60,18 +61,11 @@ pub enum AsyncPaymentsMessage {
|
|||
/// accompanying this onion message should be used to send a [`ReleaseHeldHtlc`] response, which
|
||||
/// will cause the upstream HTLC to be released.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HeldHtlcAvailable {
|
||||
/// The secret that will be used by the recipient of this message to release the held HTLC.
|
||||
pub payment_release_secret: [u8; 32],
|
||||
}
|
||||
pub struct HeldHtlcAvailable {}
|
||||
|
||||
/// Releases the HTLC corresponding to an inbound [`HeldHtlcAvailable`] message.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ReleaseHeldHtlc {
|
||||
/// Used to release the HTLC held upstream if it matches the corresponding
|
||||
/// [`HeldHtlcAvailable::payment_release_secret`].
|
||||
pub payment_release_secret: [u8; 32],
|
||||
}
|
||||
pub struct ReleaseHeldHtlc {}
|
||||
|
||||
impl OnionMessageContents for ReleaseHeldHtlc {
|
||||
fn tlv_type(&self) -> u64 {
|
||||
|
@ -87,13 +81,9 @@ impl OnionMessageContents for ReleaseHeldHtlc {
|
|||
}
|
||||
}
|
||||
|
||||
impl_writeable_tlv_based!(HeldHtlcAvailable, {
|
||||
(0, payment_release_secret, required),
|
||||
});
|
||||
impl_writeable_tlv_based!(HeldHtlcAvailable, {});
|
||||
|
||||
impl_writeable_tlv_based!(ReleaseHeldHtlc, {
|
||||
(0, payment_release_secret, required),
|
||||
});
|
||||
impl_writeable_tlv_based!(ReleaseHeldHtlc, {});
|
||||
|
||||
impl AsyncPaymentsMessage {
|
||||
/// Returns whether `tlv_type` corresponds to a TLV record for async payment messages.
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
//! Onion message testing and test utilities live here.
|
||||
|
||||
use crate::blinded_path::EmptyNodeIdLookUp;
|
||||
use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode, MessageContext, OffersContext};
|
||||
use crate::blinded_path::message::{AsyncPaymentsContext, BlindedMessagePath, MessageForwardNode, MessageContext, OffersContext};
|
||||
use crate::events::{Event, EventsProvider};
|
||||
use crate::ln::features::{ChannelFeatures, InitFeatures};
|
||||
use crate::ln::msgs::{self, DecodeError, OnionMessageHandler};
|
||||
|
@ -87,7 +87,7 @@ impl AsyncPaymentsMessageHandler for TestAsyncPaymentsMessageHandler {
|
|||
) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> {
|
||||
None
|
||||
}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc) {}
|
||||
fn release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) {}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
|
|
@ -979,6 +979,10 @@ where
|
|||
(ParsedOnionMessageContents::Offers(_), Some(MessageContext::Offers(_))) => {
|
||||
Ok(PeeledOnion::Receive(message, context, reply_path))
|
||||
}
|
||||
#[cfg(async_payments)]
|
||||
(ParsedOnionMessageContents::AsyncPayments(_), Some(MessageContext::AsyncPayments(_))) => {
|
||||
Ok(PeeledOnion::Receive(message, context, reply_path))
|
||||
}
|
||||
(ParsedOnionMessageContents::Custom(_), Some(MessageContext::Custom(_))) => {
|
||||
Ok(PeeledOnion::Receive(message, context, reply_path))
|
||||
}
|
||||
|
@ -1587,8 +1591,8 @@ where
|
|||
let context = match context {
|
||||
None => None,
|
||||
Some(MessageContext::Offers(context)) => Some(context),
|
||||
Some(MessageContext::Custom(_)) => {
|
||||
debug_assert!(false, "Shouldn't have triggered this case.");
|
||||
_ => {
|
||||
debug_assert!(false, "Checked in peel_onion_message");
|
||||
return
|
||||
}
|
||||
};
|
||||
|
@ -1608,14 +1612,22 @@ where
|
|||
},
|
||||
#[cfg(async_payments)]
|
||||
ParsedOnionMessageContents::AsyncPayments(AsyncPaymentsMessage::ReleaseHeldHtlc(msg)) => {
|
||||
self.async_payments_handler.release_held_htlc(msg);
|
||||
let context = match context {
|
||||
Some(MessageContext::AsyncPayments(context)) => context,
|
||||
Some(_) => {
|
||||
debug_assert!(false, "Checked in peel_onion_message");
|
||||
return
|
||||
},
|
||||
None => return,
|
||||
};
|
||||
self.async_payments_handler.release_held_htlc(msg, context);
|
||||
},
|
||||
ParsedOnionMessageContents::Custom(msg) => {
|
||||
let context = match context {
|
||||
None => None,
|
||||
Some(MessageContext::Custom(data)) => Some(data),
|
||||
Some(MessageContext::Offers(_)) => {
|
||||
debug_assert!(false, "Shouldn't have triggered this case.");
|
||||
_ => {
|
||||
debug_assert!(false, "Checked in peel_onion_message");
|
||||
return
|
||||
}
|
||||
};
|
||||
|
@ -1753,6 +1765,14 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(async_payments)] {
|
||||
for (message, instructions) in self.async_payments_handler.release_pending_messages() {
|
||||
let _ = self.send_onion_message_internal(
|
||||
message, instructions, format_args!("when sending AsyncPaymentsMessage")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue any initiating `CustomMessage`s to send.
|
||||
for (message, instructions) in self.custom_handler.release_pending_custom_messages() {
|
||||
let _ = self.send_onion_message_internal(
|
||||
|
|
|
@ -20,6 +20,8 @@ use crate::ln::channelmanager::{PaymentId, MIN_FINAL_CLTV_EXPIRY_DELTA, Recipien
|
|||
use crate::ln::features::{BlindedHopFeatures, Bolt11InvoiceFeatures, Bolt12InvoiceFeatures, ChannelFeatures, NodeFeatures};
|
||||
use crate::ln::msgs::{DecodeError, ErrorAction, LightningError, MAX_VALUE_MSAT};
|
||||
use crate::ln::onion_utils;
|
||||
#[cfg(async_payments)]
|
||||
use crate::offers::static_invoice::StaticInvoice;
|
||||
use crate::offers::invoice::Bolt12Invoice;
|
||||
use crate::onion_message::messenger::{DefaultMessageRouter, Destination, MessageRouter, OnionMessagePath};
|
||||
use crate::routing::gossip::{DirectedChannelInfo, EffectiveCapacity, ReadOnlyNetworkGraph, NetworkGraph, NodeId};
|
||||
|
@ -877,6 +879,16 @@ impl PaymentParameters {
|
|||
.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`].
|
||||
#[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()))
|
||||
}
|
||||
|
||||
/// Creates parameters for paying to a blinded payee from the provided blinded route hints.
|
||||
pub fn blinded(blinded_route_hints: Vec<BlindedPaymentPath>) -> Self {
|
||||
Self {
|
||||
|
|
Loading…
Add table
Reference in a new issue