use core::ops::Deref;
use core::sync::atomic::{AtomicUsize, Ordering};
use core::time::Duration;
use bitcoin::block::Header;
use bitcoin::constants::ChainHash;
use bitcoin::secp256k1::{self, PublicKey, Secp256k1};
use crate::blinded_path::message::{
AsyncPaymentsContext, BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext,
};
use crate::blinded_path::payment::{
AsyncBolt12OfferContext, BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext,
PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs,
};
use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS;
#[allow(unused_imports)]
use crate::prelude::*;
use crate::chain::BestBlock;
use crate::ln::channel_state::ChannelDetails;
use crate::ln::channelmanager::{InterceptId, PaymentId, CLTV_FAR_FAR_AWAY};
use crate::ln::inbound_payment;
use crate::offers::async_receive_offer_cache::AsyncReceiveOfferCache;
use crate::offers::invoice::{
Bolt12Invoice, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder,
UnsignedBolt12Invoice, DEFAULT_RELATIVE_EXPIRY,
};
use crate::offers::invoice_error::InvoiceError;
use crate::offers::invoice_request::{
InvoiceRequest, InvoiceRequestBuilder, VerifiedInvoiceRequest,
};
use crate::offers::nonce::Nonce;
use crate::offers::offer::{Amount, DerivedMetadata, Offer, OfferBuilder};
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::{Refund, RefundBuilder};
use crate::onion_message::async_payments::{
AsyncPaymentsMessage, HeldHtlcAvailable, OfferPaths, OfferPathsRequest, ServeStaticInvoice,
StaticInvoicePersisted,
};
use crate::onion_message::messenger::{
Destination, MessageRouter, MessageSendInstructions, Responder, PADDED_PATH_LENGTH,
};
use crate::onion_message::offers::OffersMessage;
use crate::onion_message::packet::OnionMessageContents;
use crate::routing::router::Router;
use crate::sign::{EntropySource, NodeSigner, ReceiveAuthKey};
use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder};
use crate::sync::{Mutex, RwLock};
use crate::types::payment::{PaymentHash, PaymentSecret};
use crate::util::logger::Logger;
use crate::util::ser::Writeable;
#[cfg(feature = "dnssec")]
use {
crate::blinded_path::message::DNSResolverContext,
crate::onion_message::dns_resolution::{DNSResolverMessage, DNSSECQuery, OMNameResolver},
};
pub struct OffersMessageFlow<MR: Deref, L: Deref>
where
MR::Target: MessageRouter,
L::Target: Logger,
{
chain_hash: ChainHash,
best_block: RwLock<BestBlock>,
our_network_pubkey: PublicKey,
highest_seen_timestamp: AtomicUsize,
inbound_payment_key: inbound_payment::ExpandedKey,
receive_auth_key: ReceiveAuthKey,
secp_ctx: Secp256k1<secp256k1::All>,
message_router: MR,
#[cfg(not(any(test, feature = "_test_utils")))]
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)>>,
async_receive_offer_cache: Mutex<AsyncReceiveOfferCache>,
#[cfg(feature = "dnssec")]
pub(crate) hrn_resolver: OMNameResolver,
#[cfg(feature = "dnssec")]
pending_dns_onion_messages: Mutex<Vec<(DNSResolverMessage, MessageSendInstructions)>>,
logger: L,
}
impl<MR: Deref, L: Deref> OffersMessageFlow<MR, L>
where
MR::Target: MessageRouter,
L::Target: Logger,
{
pub fn new(
chain_hash: ChainHash, best_block: BestBlock, our_network_pubkey: PublicKey,
current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey,
receive_auth_key: ReceiveAuthKey, secp_ctx: Secp256k1<secp256k1::All>, message_router: MR,
logger: L,
) -> Self {
Self {
chain_hash,
best_block: RwLock::new(best_block),
our_network_pubkey,
highest_seen_timestamp: AtomicUsize::new(current_timestamp as usize),
inbound_payment_key,
receive_auth_key,
secp_ctx,
message_router,
pending_offers_messages: Mutex::new(Vec::new()),
pending_async_payments_messages: Mutex::new(Vec::new()),
#[cfg(feature = "dnssec")]
hrn_resolver: OMNameResolver::new(current_timestamp, best_block.height),
#[cfg(feature = "dnssec")]
pending_dns_onion_messages: Mutex::new(Vec::new()),
async_receive_offer_cache: Mutex::new(AsyncReceiveOfferCache::new()),
logger,
}
}
pub fn with_async_payments_offers_cache(
mut self, async_receive_offer_cache: AsyncReceiveOfferCache,
) -> Self {
self.async_receive_offer_cache = Mutex::new(async_receive_offer_cache);
self
}
pub fn set_paths_to_static_invoice_server(
&self, paths_to_static_invoice_server: Vec<BlindedMessagePath>,
peers: Vec<MessageForwardNode>,
) -> Result<(), ()> {
let mut cache = self.async_receive_offer_cache.lock().unwrap();
cache.set_paths_to_static_invoice_server(paths_to_static_invoice_server.clone())?;
core::mem::drop(cache);
let _ = self.check_refresh_async_offers(peers, false);
Ok(())
}
fn get_our_node_id(&self) -> PublicKey {
self.our_network_pubkey
}
fn get_receive_auth_key(&self) -> ReceiveAuthKey {
self.receive_auth_key
}
fn duration_since_epoch(&self) -> Duration {
#[cfg(not(feature = "std"))]
let now = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
#[cfg(feature = "std")]
let now = std::time::SystemTime::now()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.expect("SystemTime::now() should come after SystemTime::UNIX_EPOCH");
now
}
pub fn best_block_updated(&self, header: &Header, _height: u32) {
let timestamp = &self.highest_seen_timestamp;
let block_time = header.time as usize;
loop {
let old_serial = timestamp.load(Ordering::Acquire);
if old_serial >= block_time {
break;
}
if timestamp
.compare_exchange(old_serial, block_time, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
{
break;
}
}
#[cfg(feature = "dnssec")]
{
let updated_time = timestamp.load(Ordering::Acquire) as u32;
self.hrn_resolver.new_best_block(_height, updated_time);
}
}
}
pub const MAX_STATIC_INVOICE_SIZE_BYTES: usize = 5 * 1024;
const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10;
#[cfg(test)]
pub(crate) const TEST_OFFERS_MESSAGE_REQUEST_LIMIT: usize = OFFERS_MESSAGE_REQUEST_LIMIT;
const TEMP_REPLY_PATH_RELATIVE_EXPIRY: Duration = Duration::from_secs(2 * 60 * 60);
#[cfg(test)]
pub(crate) const TEST_TEMP_REPLY_PATH_RELATIVE_EXPIRY: Duration = TEMP_REPLY_PATH_RELATIVE_EXPIRY;
const DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration = Duration::from_secs(365 * 24 * 60 * 60);
#[cfg(test)]
pub(crate) const TEST_DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY: Duration =
DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY;
impl<MR: Deref, L: Deref> OffersMessageFlow<MR, L>
where
MR::Target: MessageRouter,
L::Target: Logger,
{
pub fn blinded_paths_for_async_recipient(
&self, recipient_id: Vec<u8>, relative_expiry: Option<Duration>,
peers: Vec<MessageForwardNode>,
) -> Result<Vec<BlindedMessagePath>, ()> {
if recipient_id.len() > 1024 {
log_trace!(self.logger, "Async recipient ID exceeds 1024 bytes");
return Err(());
}
let path_absolute_expiry =
relative_expiry.map(|exp| exp.saturating_add(self.duration_since_epoch()));
let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPathsRequest {
recipient_id,
path_absolute_expiry,
});
self.create_blinded_paths(peers, context)
}
fn create_blinded_paths(
&self, peers: Vec<MessageForwardNode>, context: MessageContext,
) -> Result<Vec<BlindedMessagePath>, ()> {
let recipient = self.get_our_node_id();
let receive_key = self.get_receive_auth_key();
let secp_ctx = &self.secp_ctx;
self.message_router
.create_blinded_paths(recipient, receive_key, context, peers, secp_ctx)
.and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(()))
}
fn create_blinded_payment_paths<ES: Deref, R: Deref>(
&self, router: &R, entropy_source: ES, usable_channels: Vec<ChannelDetails>,
amount_msats: Option<u64>, payment_secret: PaymentSecret, payment_context: PaymentContext,
relative_expiry_seconds: u32,
) -> Result<Vec<BlindedPaymentPath>, ()>
where
ES::Target: EntropySource,
R::Target: Router,
{
let expanded_key = &self.inbound_payment_key;
let entropy = &*entropy_source;
let secp_ctx = &self.secp_ctx;
let payee_node_id = self.get_our_node_id();
const SECONDS_PER_BLOCK: u32 = 9 * 60;
let relative_expiry_blocks = relative_expiry_seconds / SECONDS_PER_BLOCK;
let max_cltv_expiry = core::cmp::max(relative_expiry_blocks, CLTV_FAR_FAR_AWAY)
.saturating_add(LATENCY_GRACE_PERIOD_BLOCKS)
.saturating_add(self.best_block.read().unwrap().height);
let payee_tlvs = UnauthenticatedReceiveTlvs {
payment_secret,
payment_constraints: PaymentConstraints { max_cltv_expiry, htlc_minimum_msat: 1 },
payment_context,
};
let nonce = Nonce::from_entropy_source(entropy);
let payee_tlvs = payee_tlvs.authenticate(nonce, expanded_key);
router.create_blinded_payment_paths(
payee_node_id,
usable_channels,
payee_tlvs,
amount_msats,
secp_ctx,
)
}
#[cfg(test)]
pub(crate) fn test_create_blinded_payment_paths<ES: Deref, R: Deref>(
&self, router: &R, entropy_source: ES, usable_channels: Vec<ChannelDetails>,
amount_msats: Option<u64>, payment_secret: PaymentSecret, payment_context: PaymentContext,
relative_expiry_seconds: u32,
) -> Result<Vec<BlindedPaymentPath>, ()>
where
ES::Target: EntropySource,
R::Target: Router,
{
self.create_blinded_payment_paths(
router,
entropy_source,
usable_channels,
amount_msats,
payment_secret,
payment_context,
relative_expiry_seconds,
)
}
}
fn enqueue_onion_message_with_reply_paths<T: OnionMessageContents + Clone>(
message: T, message_paths: &[BlindedMessagePath], reply_paths: Vec<BlindedMessagePath>,
queue: &mut Vec<(T, MessageSendInstructions)>,
) {
reply_paths
.iter()
.flat_map(|reply_path| message_paths.iter().map(move |path| (path, reply_path)))
.take(OFFERS_MESSAGE_REQUEST_LIMIT)
.for_each(|(path, reply_path)| {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::BlindedPath(path.clone()),
reply_path: reply_path.clone(),
};
queue.push((message.clone(), instructions));
});
}
pub enum InvreqResponseInstructions {
SendInvoice(VerifiedInvoiceRequest),
SendStaticInvoice {
recipient_id: Vec<u8>,
invoice_slot: u16,
invoice_request: InvoiceRequest,
},
}
pub enum HeldHtlcReplyPath {
ToUs {
payment_id: PaymentId,
peers: Vec<MessageForwardNode>,
},
ToCounterparty {
path: BlindedMessagePath,
},
}
impl<MR: Deref, L: Deref> OffersMessageFlow<MR, L>
where
MR::Target: MessageRouter,
L::Target: Logger,
{
pub fn verify_invoice_request(
&self, invoice_request: InvoiceRequest, context: Option<OffersContext>,
) -> Result<InvreqResponseInstructions, ()> {
let secp_ctx = &self.secp_ctx;
let expanded_key = &self.inbound_payment_key;
let nonce = match context {
None if invoice_request.metadata().is_some() => None,
Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce),
Some(OffersContext::StaticInvoiceRequested {
recipient_id,
invoice_slot,
path_absolute_expiry,
}) => {
if path_absolute_expiry < self.duration_since_epoch() {
log_trace!(self.logger, "Static invoice request has expired");
return Err(());
}
return Ok(InvreqResponseInstructions::SendStaticInvoice {
recipient_id,
invoice_slot,
invoice_request,
});
},
_ => return Err(()),
};
let invoice_request = match nonce {
Some(nonce) => {
invoice_request.verify_using_recipient_data(nonce, expanded_key, secp_ctx)
},
None => invoice_request.verify_using_metadata(expanded_key, secp_ctx),
}?;
Ok(InvreqResponseInstructions::SendInvoice(invoice_request))
}
pub fn verify_bolt12_invoice(
&self, invoice: &Bolt12Invoice, context: Option<&OffersContext>,
) -> Result<PaymentId, ()> {
let secp_ctx = &self.secp_ctx;
let expanded_key = &self.inbound_payment_key;
match context {
None if invoice.is_for_refund_without_paths() => {
invoice.verify_using_metadata(expanded_key, secp_ctx)
},
Some(&OffersContext::OutboundPayment { payment_id, nonce, .. }) => {
invoice.verify_using_payer_data(payment_id, nonce, expanded_key, secp_ctx)
},
_ => Err(()),
}
}
pub fn verify_inbound_async_payment_context(
&self, context: AsyncPaymentsContext,
) -> Result<(), ()> {
match context {
AsyncPaymentsContext::InboundPayment { path_absolute_expiry } => {
if self.duration_since_epoch() > path_absolute_expiry {
return Err(());
}
Ok(())
},
_ => Err(()),
}
}
fn create_offer_builder_intern<ES: Deref, PF, I>(
&self, entropy_source: ES, make_paths: PF,
) -> Result<(OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError>
where
ES::Target: EntropySource,
PF: FnOnce(
PublicKey,
MessageContext,
&secp256k1::Secp256k1<secp256k1::All>,
) -> Result<I, Bolt12SemanticError>,
I: IntoIterator<Item = BlindedMessagePath>,
{
let node_id = self.get_our_node_id();
let expanded_key = &self.inbound_payment_key;
let entropy = entropy_source;
let secp_ctx = &self.secp_ctx;
let nonce = Nonce::from_entropy_source(entropy);
let context = MessageContext::Offers(OffersContext::InvoiceRequest { nonce });
let mut builder =
OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx)
.chain_hash(self.chain_hash);
for path in make_paths(node_id, context, secp_ctx)? {
builder = builder.path(path)
}
Ok((builder.into(), nonce))
}
pub fn create_offer_builder<ES: Deref>(
&self, entropy_source: ES, peers: Vec<MessageForwardNode>,
) -> Result<OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
self.create_offer_builder_intern(&*entropy_source, |_, context, _| {
self.create_blinded_paths(peers, context)
.map(|paths| paths.into_iter().take(1))
.map_err(|_| Bolt12SemanticError::MissingPaths)
})
.map(|(builder, _)| builder)
}
pub fn create_offer_builder_using_router<ME: Deref, ES: Deref>(
&self, router: ME, entropy_source: ES, peers: Vec<MessageForwardNode>,
) -> Result<OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Bolt12SemanticError>
where
ME::Target: MessageRouter,
ES::Target: EntropySource,
{
let receive_key = self.get_receive_auth_key();
self.create_offer_builder_intern(&*entropy_source, |node_id, context, secp_ctx| {
router
.create_blinded_paths(node_id, receive_key, context, peers, secp_ctx)
.map(|paths| paths.into_iter().take(1))
.map_err(|_| Bolt12SemanticError::MissingPaths)
})
.map(|(builder, _)| builder)
}
pub fn create_async_receive_offer_builder<ES: Deref>(
&self, entropy_source: ES, message_paths_to_always_online_node: Vec<BlindedMessagePath>,
) -> Result<(OfferBuilder<'_, DerivedMetadata, secp256k1::All>, Nonce), Bolt12SemanticError>
where
ES::Target: EntropySource,
{
self.create_offer_builder_intern(&*entropy_source, |_, _, _| {
Ok(message_paths_to_always_online_node)
})
}
fn create_refund_builder_intern<ES: Deref, PF, I>(
&self, entropy_source: ES, make_paths: PF, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId,
) -> Result<RefundBuilder<'_, secp256k1::All>, Bolt12SemanticError>
where
ES::Target: EntropySource,
PF: FnOnce(
PublicKey,
MessageContext,
&secp256k1::Secp256k1<secp256k1::All>,
) -> Result<I, Bolt12SemanticError>,
I: IntoIterator<Item = BlindedMessagePath>,
{
let node_id = self.get_our_node_id();
let expanded_key = &self.inbound_payment_key;
let entropy = &*entropy_source;
let secp_ctx = &self.secp_ctx;
let nonce = Nonce::from_entropy_source(entropy);
let context = MessageContext::Offers(OffersContext::OutboundPayment { payment_id, nonce });
let mut builder = RefundBuilder::deriving_signing_pubkey(
node_id,
expanded_key,
nonce,
secp_ctx,
amount_msats,
payment_id,
)?
.chain_hash(self.chain_hash)
.absolute_expiry(absolute_expiry);
for path in make_paths(node_id, context, secp_ctx)? {
builder = builder.path(path);
}
Ok(builder.into())
}
pub fn create_refund_builder<ES: Deref>(
&self, entropy_source: ES, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId, peers: Vec<MessageForwardNode>,
) -> Result<RefundBuilder<'_, secp256k1::All>, Bolt12SemanticError>
where
ES::Target: EntropySource,
{
self.create_refund_builder_intern(
&*entropy_source,
|_, context, _| {
self.create_blinded_paths(peers, context)
.map(|paths| paths.into_iter().take(1))
.map_err(|_| Bolt12SemanticError::MissingPaths)
},
amount_msats,
absolute_expiry,
payment_id,
)
}
pub fn create_refund_builder_using_router<ES: Deref, ME: Deref>(
&self, router: ME, entropy_source: ES, amount_msats: u64, absolute_expiry: Duration,
payment_id: PaymentId, peers: Vec<MessageForwardNode>,
) -> Result<RefundBuilder<'_, secp256k1::All>, Bolt12SemanticError>
where
ME::Target: MessageRouter,
ES::Target: EntropySource,
{
let receive_key = self.get_receive_auth_key();
self.create_refund_builder_intern(
&*entropy_source,
|node_id, context, secp_ctx| {
router
.create_blinded_paths(node_id, receive_key, context, peers, secp_ctx)
.map(|paths| paths.into_iter().take(1))
.map_err(|_| Bolt12SemanticError::MissingPaths)
},
amount_msats,
absolute_expiry,
payment_id,
)
}
pub fn create_invoice_request_builder<'a>(
&'a self, offer: &'a Offer, nonce: Nonce, payment_id: PaymentId,
) -> Result<InvoiceRequestBuilder<'a, 'a, secp256k1::All>, Bolt12SemanticError> {
let expanded_key = &self.inbound_payment_key;
let secp_ctx = &self.secp_ctx;
let builder: InvoiceRequestBuilder<secp256k1::All> =
offer.request_invoice(expanded_key, nonce, secp_ctx, payment_id)?.into();
let builder = builder.chain_hash(self.chain_hash)?;
Ok(builder)
}
pub fn create_static_invoice_builder<'a, ES: Deref, R: Deref>(
&self, router: &R, entropy_source: ES, offer: &'a Offer, offer_nonce: Nonce,
payment_secret: PaymentSecret, relative_expiry_secs: u32,
usable_channels: Vec<ChannelDetails>, peers: Vec<MessageForwardNode>,
) -> Result<StaticInvoiceBuilder<'a>, Bolt12SemanticError>
where
ES::Target: EntropySource,
R::Target: Router,
{
let expanded_key = &self.inbound_payment_key;
let entropy = &*entropy_source;
let secp_ctx = &self.secp_ctx;
let payment_context =
PaymentContext::AsyncBolt12Offer(AsyncBolt12OfferContext { offer_nonce });
let amount_msat = offer.amount().and_then(|amount| match amount {
Amount::Bitcoin { amount_msats } => Some(amount_msats),
Amount::Currency { .. } => None,
});
let created_at = self.duration_since_epoch();
let payment_paths = self
.create_blinded_payment_paths(
router,
entropy,
usable_channels,
amount_msat,
payment_secret,
payment_context,
relative_expiry_secs,
)
.map_err(|()| Bolt12SemanticError::MissingPaths)?;
let path_absolute_expiry = Duration::from_secs(inbound_payment::calculate_absolute_expiry(
created_at.as_secs(),
relative_expiry_secs,
));
let context = MessageContext::AsyncPayments(AsyncPaymentsContext::InboundPayment {
path_absolute_expiry,
});
let async_receive_message_paths = self
.create_blinded_paths(peers, context)
.map_err(|()| Bolt12SemanticError::MissingPaths)?;
StaticInvoiceBuilder::for_offer_using_derived_keys(
offer,
payment_paths,
async_receive_message_paths,
created_at,
expanded_key,
offer_nonce,
secp_ctx,
)
.map(|inv| inv.allow_mpp().relative_expiry(relative_expiry_secs))
}
pub fn create_invoice_builder_from_refund<'a, ES: Deref, R: Deref>(
&'a self, router: &R, entropy_source: ES, refund: &'a Refund, payment_hash: PaymentHash,
payment_secret: PaymentSecret, usable_channels: Vec<ChannelDetails>,
) -> Result<InvoiceBuilder<'a, DerivedSigningPubkey>, Bolt12SemanticError>
where
ES::Target: EntropySource,
R::Target: Router,
{
if refund.chain() != self.chain_hash {
return Err(Bolt12SemanticError::UnsupportedChain);
}
let expanded_key = &self.inbound_payment_key;
let entropy = &*entropy_source;
let amount_msats = refund.amount_msats();
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
let payment_paths = self
.create_blinded_payment_paths(
router,
entropy,
usable_channels,
Some(amount_msats),
payment_secret,
payment_context,
relative_expiry,
)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
#[cfg(feature = "std")]
let builder = refund.respond_using_derived_keys(
payment_paths,
payment_hash,
expanded_key,
entropy,
)?;
#[cfg(not(feature = "std"))]
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
#[cfg(not(feature = "std"))]
let builder = refund.respond_using_derived_keys_no_std(
payment_paths,
payment_hash,
created_at,
expanded_key,
entropy,
)?;
Ok(builder.into())
}
pub fn create_response_for_invoice_request<ES: Deref, NS: Deref, R: Deref>(
&self, signer: &NS, router: &R, entropy_source: ES,
invoice_request: VerifiedInvoiceRequest, amount_msats: u64, payment_hash: PaymentHash,
payment_secret: PaymentSecret, usable_channels: Vec<ChannelDetails>,
) -> (OffersMessage, Option<MessageContext>)
where
ES::Target: EntropySource,
NS::Target: NodeSigner,
R::Target: Router,
{
let entropy = &*entropy_source;
let secp_ctx = &self.secp_ctx;
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;
let context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
offer_id: invoice_request.offer_id,
invoice_request: invoice_request.fields(),
});
let payment_paths = match self.create_blinded_payment_paths(
router,
entropy,
usable_channels,
Some(amount_msats),
payment_secret,
context,
relative_expiry,
) {
Ok(paths) => paths,
Err(_) => {
let error = InvoiceError::from(Bolt12SemanticError::MissingPaths);
return (OffersMessage::InvoiceError(error.into()), None);
},
};
#[cfg(not(feature = "std"))]
let created_at = Duration::from_secs(self.highest_seen_timestamp.load(Ordering::Acquire) as u64);
let response = if invoice_request.keys.is_some() {
#[cfg(feature = "std")]
let builder = invoice_request.respond_using_derived_keys(payment_paths, payment_hash);
#[cfg(not(feature = "std"))]
let builder = invoice_request.respond_using_derived_keys_no_std(
payment_paths,
payment_hash,
created_at,
);
builder
.map(InvoiceBuilder::<DerivedSigningPubkey>::from)
.and_then(|builder| builder.allow_mpp().build_and_sign(secp_ctx))
.map_err(InvoiceError::from)
} else {
#[cfg(feature = "std")]
let builder = invoice_request.respond_with(payment_paths, payment_hash);
#[cfg(not(feature = "std"))]
let builder = invoice_request.respond_with_no_std(payment_paths, payment_hash, created_at);
builder
.map(InvoiceBuilder::<ExplicitSigningPubkey>::from)
.and_then(|builder| builder.allow_mpp().build())
.map_err(InvoiceError::from)
.and_then(|invoice| {
#[cfg(c_bindings)]
let mut invoice = invoice;
invoice
.sign(|invoice: &UnsignedBolt12Invoice| signer.sign_bolt12_invoice(invoice))
.map_err(InvoiceError::from)
})
};
match response {
Ok(invoice) => {
let context =
MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
(OffersMessage::Invoice(invoice), Some(context))
},
Err(error) => (OffersMessage::InvoiceError(error.into()), None),
}
}
pub fn enqueue_invoice_request(
&self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Nonce,
peers: Vec<MessageForwardNode>,
) -> Result<(), Bolt12SemanticError> {
let context = MessageContext::Offers(OffersContext::OutboundPayment { payment_id, nonce });
let reply_paths = self
.create_blinded_paths(peers, context)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if !invoice_request.paths().is_empty() {
let message = OffersMessage::InvoiceRequest(invoice_request.clone());
enqueue_onion_message_with_reply_paths(
message,
invoice_request.paths(),
reply_paths,
&mut pending_offers_messages,
);
} else if let Some(node_id) = invoice_request.issuer_signing_pubkey() {
for reply_path in reply_paths {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::Node(node_id),
reply_path,
};
let message = OffersMessage::InvoiceRequest(invoice_request.clone());
pending_offers_messages.push((message, instructions));
}
} else {
debug_assert!(false);
return Err(Bolt12SemanticError::MissingIssuerSigningPubkey);
}
Ok(())
}
pub fn enqueue_invoice(
&self, invoice: Bolt12Invoice, refund: &Refund, peers: Vec<MessageForwardNode>,
) -> Result<(), Bolt12SemanticError> {
let payment_hash = invoice.payment_hash();
let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash });
let reply_paths = self
.create_blinded_paths(peers, context)
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
if refund.paths().is_empty() {
for reply_path in reply_paths {
let instructions = MessageSendInstructions::WithSpecifiedReplyPath {
destination: Destination::Node(refund.payer_signing_pubkey()),
reply_path,
};
let message = OffersMessage::Invoice(invoice.clone());
pending_offers_messages.push((message, instructions));
}
} else {
let message = OffersMessage::Invoice(invoice);
enqueue_onion_message_with_reply_paths(
message,
refund.paths(),
reply_paths,
&mut pending_offers_messages,
);
}
Ok(())
}
pub fn enqueue_static_invoice(
&self, invoice: StaticInvoice, responder: Responder,
) -> Result<(), Bolt12SemanticError> {
let duration_since_epoch = self.duration_since_epoch();
if invoice.is_expired_no_std(duration_since_epoch) {
return Err(Bolt12SemanticError::AlreadyExpired);
}
if invoice.is_offer_expired_no_std(duration_since_epoch) {
return Err(Bolt12SemanticError::AlreadyExpired);
}
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
let message = OffersMessage::StaticInvoice(invoice);
pending_offers_messages.push((message, responder.respond().into_instructions()));
Ok(())
}
pub fn enqueue_invoice_request_to_forward(
&self, invoice_request: InvoiceRequest, destination: BlindedMessagePath,
reply_path: Responder,
) {
let mut pending_offers_messages = self.pending_offers_messages.lock().unwrap();
let message = OffersMessage::InvoiceRequest(invoice_request);
let instructions = MessageSendInstructions::ForwardedMessage {
destination: Destination::BlindedPath(destination),
reply_path: Some(reply_path.into_blinded_path()),
};
pending_offers_messages.push((message, instructions));
}
pub fn enqueue_held_htlc_available(
&self, invoice: &StaticInvoice, reply_path_params: HeldHtlcReplyPath,
) -> Result<(), Bolt12SemanticError> {
let reply_path_terminates_at_us =
matches!(reply_path_params, HeldHtlcReplyPath::ToUs { .. });
let reply_paths = match reply_path_params {
HeldHtlcReplyPath::ToUs { payment_id, peers } => {
let context =
MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment {
payment_id,
});
self.create_blinded_paths(peers, context)
.map_err(|_| {
log_trace!(self.logger, "Failed to create blinded paths when enqueueing held_htlc_available message");
Bolt12SemanticError::MissingPaths
})?
},
HeldHtlcReplyPath::ToCounterparty { path } => vec![path],
};
log_trace!(
self.logger,
"Sending held_htlc_available message for async HTLC, with reply_path terminating at {}",
if reply_path_terminates_at_us { "our node" } else { "our always-online counterparty" }
);
let mut pending_async_payments_messages =
self.pending_async_payments_messages.lock().unwrap();
let message = AsyncPaymentsMessage::HeldHtlcAvailable(HeldHtlcAvailable {});
enqueue_onion_message_with_reply_paths(
message,
invoice.message_paths(),
reply_paths,
&mut pending_async_payments_messages,
);
Ok(())
}
pub fn path_for_release_held_htlc<ES: Deref>(
&self, intercept_id: InterceptId, prev_outbound_scid_alias: u64, htlc_id: u64, entropy: ES,
) -> BlindedMessagePath
where
ES::Target: EntropySource,
{
let context = MessageContext::AsyncPayments(AsyncPaymentsContext::ReleaseHeldHtlc {
intercept_id,
prev_outbound_scid_alias,
htlc_id,
});
let num_dummy_hops = PADDED_PATH_LENGTH.saturating_sub(1);
BlindedMessagePath::new_with_dummy_hops(
&[],
self.get_our_node_id(),
num_dummy_hops,
self.receive_auth_key,
context,
&*entropy,
&self.secp_ctx,
)
}
#[cfg(feature = "dnssec")]
pub fn enqueue_dns_onion_message(
&self, message: DNSSECQuery, context: DNSResolverContext, dns_resolvers: Vec<Destination>,
peers: Vec<MessageForwardNode>,
) -> Result<(), Bolt12SemanticError> {
let reply_paths = self
.create_blinded_paths(peers, MessageContext::DNSResolver(context))
.map_err(|_| Bolt12SemanticError::MissingPaths)?;
let message_params = dns_resolvers
.iter()
.flat_map(|destination| reply_paths.iter().map(move |path| (path, destination)))
.take(OFFERS_MESSAGE_REQUEST_LIMIT);
for (reply_path, destination) in message_params {
self.pending_dns_onion_messages.lock().unwrap().push((
DNSResolverMessage::DNSSECQuery(message.clone()),
MessageSendInstructions::WithSpecifiedReplyPath {
destination: destination.clone(),
reply_path: reply_path.clone(),
},
));
}
Ok(())
}
pub fn release_pending_offers_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> {
core::mem::take(&mut self.pending_offers_messages.lock().unwrap())
}
pub fn release_pending_async_messages(
&self,
) -> Vec<(AsyncPaymentsMessage, MessageSendInstructions)> {
core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap())
}
#[cfg(feature = "dnssec")]
pub fn release_pending_dns_messages(
&self,
) -> Vec<(DNSResolverMessage, MessageSendInstructions)> {
core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap())
}
pub fn get_async_receive_offer(&self) -> Result<(Offer, bool), ()> {
let mut cache = self.async_receive_offer_cache.lock().unwrap();
cache.get_async_receive_offer(self.duration_since_epoch())
}
#[cfg(test)]
pub(crate) fn test_get_async_receive_offers(&self) -> Vec<Offer> {
self.async_receive_offer_cache.lock().unwrap().test_get_payable_offers()
}
pub fn check_refresh_async_receive_offer_cache<ES: Deref, R: Deref>(
&self, peers: Vec<MessageForwardNode>, usable_channels: Vec<ChannelDetails>, entropy: ES,
router: R, timer_tick_occurred: bool,
) -> Result<(), ()>
where
ES::Target: EntropySource,
R::Target: Router,
{
{
let cache = self.async_receive_offer_cache.lock().unwrap();
if cache.paths_to_static_invoice_server().is_empty() {
return Ok(());
}
}
self.check_refresh_async_offers(peers.clone(), timer_tick_occurred)?;
if timer_tick_occurred {
self.check_refresh_static_invoices(peers, usable_channels, entropy, router);
}
Ok(())
}
fn check_refresh_async_offers(
&self, peers: Vec<MessageForwardNode>, timer_tick_occurred: bool,
) -> Result<(), ()> {
let duration_since_epoch = self.duration_since_epoch();
let mut cache = self.async_receive_offer_cache.lock().unwrap();
let needs_new_offer_slot =
match cache.prune_expired_offers(duration_since_epoch, timer_tick_occurred) {
Some(idx) => idx,
None => return Ok(()),
};
let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OfferPaths {
path_absolute_expiry: duration_since_epoch
.saturating_add(TEMP_REPLY_PATH_RELATIVE_EXPIRY),
invoice_slot: needs_new_offer_slot,
});
let reply_paths = match self.create_blinded_paths(peers, context) {
Ok(paths) => paths,
Err(()) => {
log_error!(
self.logger,
"Failed to create blinded paths for OfferPathsRequest message"
);
return Err(());
},
};
cache.new_offers_requested();
let mut pending_async_payments_messages =
self.pending_async_payments_messages.lock().unwrap();
let message = AsyncPaymentsMessage::OfferPathsRequest(OfferPathsRequest {
invoice_slot: needs_new_offer_slot,
});
enqueue_onion_message_with_reply_paths(
message,
cache.paths_to_static_invoice_server(),
reply_paths,
&mut pending_async_payments_messages,
);
Ok(())
}
fn check_refresh_static_invoices<ES: Deref, R: Deref>(
&self, peers: Vec<MessageForwardNode>, usable_channels: Vec<ChannelDetails>, entropy: ES,
router: R,
) where
ES::Target: EntropySource,
R::Target: Router,
{
let mut serve_static_invoice_msgs = Vec::new();
{
let duration_since_epoch = self.duration_since_epoch();
let cache = self.async_receive_offer_cache.lock().unwrap();
for offer_and_metadata in cache.offers_needing_invoice_refresh(duration_since_epoch) {
let (offer, offer_nonce, update_static_invoice_path) = offer_and_metadata;
let (invoice, forward_invreq_path) = match self.create_static_invoice_for_server(
offer,
offer_nonce,
peers.clone(),
usable_channels.clone(),
&*entropy,
&*router,
) {
Ok((invoice, path)) => (invoice, path),
Err(()) => continue,
};
let reply_path_context = {
MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted {
invoice_created_at: invoice.created_at(),
offer_id: offer.id(),
})
};
let serve_invoice_message = ServeStaticInvoice {
invoice,
forward_invoice_request_path: forward_invreq_path,
};
serve_static_invoice_msgs.push((
serve_invoice_message,
update_static_invoice_path.clone(),
reply_path_context,
));
}
}
for (serve_invoice_msg, serve_invoice_path, reply_path_ctx) in serve_static_invoice_msgs {
let reply_paths = match self.create_blinded_paths(peers.clone(), reply_path_ctx) {
Ok(paths) => paths,
Err(()) => continue,
};
let message = AsyncPaymentsMessage::ServeStaticInvoice(serve_invoice_msg);
enqueue_onion_message_with_reply_paths(
message,
&[serve_invoice_path.into_blinded_path()],
reply_paths,
&mut self.pending_async_payments_messages.lock().unwrap(),
);
}
}
pub fn handle_offer_paths_request(
&self, request: &OfferPathsRequest, context: AsyncPaymentsContext,
peers: Vec<MessageForwardNode>,
) -> Option<(OfferPaths, MessageContext)> {
let duration_since_epoch = self.duration_since_epoch();
let recipient_id = match context {
AsyncPaymentsContext::OfferPathsRequest { recipient_id, path_absolute_expiry } => {
if duration_since_epoch > path_absolute_expiry.unwrap_or(Duration::MAX) {
return None;
}
recipient_id
},
_ => return None,
};
let (offer_paths, paths_expiry) = {
let path_absolute_expiry =
duration_since_epoch.saturating_add(DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY);
let context = MessageContext::Offers(OffersContext::StaticInvoiceRequested {
recipient_id: recipient_id.clone(),
path_absolute_expiry,
invoice_slot: request.invoice_slot,
});
match self.create_blinded_paths(peers, context) {
Ok(paths) => (paths, path_absolute_expiry),
Err(()) => {
log_error!(
self.logger,
"Failed to create blinded paths for OfferPaths message"
);
return None;
},
}
};
let reply_path_context = {
let path_absolute_expiry =
duration_since_epoch.saturating_add(DEFAULT_ASYNC_RECEIVE_OFFER_EXPIRY);
MessageContext::AsyncPayments(AsyncPaymentsContext::ServeStaticInvoice {
recipient_id,
invoice_slot: request.invoice_slot,
path_absolute_expiry,
})
};
let offer_paths_om =
OfferPaths { paths: offer_paths, paths_absolute_expiry: Some(paths_expiry.as_secs()) };
return Some((offer_paths_om, reply_path_context));
}
pub fn handle_offer_paths<ES: Deref, R: Deref>(
&self, message: OfferPaths, context: AsyncPaymentsContext, responder: Responder,
peers: Vec<MessageForwardNode>, usable_channels: Vec<ChannelDetails>, entropy: ES,
router: R,
) -> Option<(ServeStaticInvoice, MessageContext)>
where
ES::Target: EntropySource,
R::Target: Router,
{
let duration_since_epoch = self.duration_since_epoch();
let invoice_slot = match context {
AsyncPaymentsContext::OfferPaths { invoice_slot, path_absolute_expiry } => {
if duration_since_epoch > path_absolute_expiry {
return None;
}
invoice_slot
},
_ => return None,
};
{
let mut cache = self.async_receive_offer_cache.lock().unwrap();
cache.prune_expired_offers(duration_since_epoch, false);
if !cache.should_build_offer_with_paths(
&message.paths[..],
message.paths_absolute_expiry,
invoice_slot,
duration_since_epoch,
) {
return None;
}
}
let (mut offer_builder, offer_nonce) =
match self.create_async_receive_offer_builder(&*entropy, message.paths) {
Ok((builder, nonce)) => (builder, nonce),
Err(_) => return None, };
if let Some(paths_absolute_expiry) = message.paths_absolute_expiry {
offer_builder =
offer_builder.absolute_expiry(Duration::from_secs(paths_absolute_expiry));
}
let (offer_id, offer) = match offer_builder.build() {
Ok(offer) => (offer.id(), offer),
Err(_) => {
log_error!(self.logger, "Failed to build async receive offer");
debug_assert!(false);
return None;
},
};
let (invoice, forward_invoice_request_path) = match self.create_static_invoice_for_server(
&offer,
offer_nonce,
peers,
usable_channels,
&*entropy,
router,
) {
Ok(res) => res,
Err(()) => {
log_error!(self.logger, "Failed to create static invoice for server");
return None;
},
};
if let Err(()) = self.async_receive_offer_cache.lock().unwrap().cache_pending_offer(
offer,
message.paths_absolute_expiry,
offer_nonce,
responder,
duration_since_epoch,
invoice_slot,
) {
log_error!(self.logger, "Failed to cache pending offer");
return None;
}
let reply_path_context = {
MessageContext::AsyncPayments(AsyncPaymentsContext::StaticInvoicePersisted {
offer_id,
invoice_created_at: invoice.created_at(),
})
};
let serve_invoice_message = ServeStaticInvoice { invoice, forward_invoice_request_path };
Some((serve_invoice_message, reply_path_context))
}
fn create_static_invoice_for_server<ES: Deref, R: Deref>(
&self, offer: &Offer, offer_nonce: Nonce, peers: Vec<MessageForwardNode>,
usable_channels: Vec<ChannelDetails>, entropy: ES, router: R,
) -> Result<(StaticInvoice, BlindedMessagePath), ()>
where
ES::Target: EntropySource,
R::Target: Router,
{
let expanded_key = &self.inbound_payment_key;
let duration_since_epoch = self.duration_since_epoch();
let secp_ctx = &self.secp_ctx;
let offer_relative_expiry = offer
.absolute_expiry()
.map(|exp| exp.saturating_sub(duration_since_epoch).as_secs())
.map(|exp_u64| exp_u64.try_into().unwrap_or(u32::MAX))
.unwrap_or(u32::MAX);
let payment_secret = inbound_payment::create_for_spontaneous_payment(
expanded_key,
None, offer_relative_expiry,
duration_since_epoch.as_secs(),
None,
)?;
let invoice = self
.create_static_invoice_builder(
&router,
&*entropy,
&offer,
offer_nonce,
payment_secret,
offer_relative_expiry,
usable_channels,
peers.clone(),
)
.and_then(|builder| builder.build_and_sign(secp_ctx))
.map_err(|_| ())?;
let context = MessageContext::Offers(OffersContext::InvoiceRequest { nonce: offer_nonce });
let forward_invoice_request_path = self
.create_blinded_paths(peers, context)
.and_then(|paths| paths.into_iter().next().ok_or(()))?;
Ok((invoice, forward_invoice_request_path))
}
pub fn verify_serve_static_invoice_message(
&self, message: &ServeStaticInvoice, context: AsyncPaymentsContext,
) -> Result<(Vec<u8>, u16), ()> {
if message.invoice.is_expired_no_std(self.duration_since_epoch()) {
log_trace!(self.logger, "Received expired StaticInvoice");
return Err(());
}
if message.invoice.serialized_length() > MAX_STATIC_INVOICE_SIZE_BYTES {
return Err(());
}
match context {
AsyncPaymentsContext::ServeStaticInvoice {
recipient_id,
invoice_slot,
path_absolute_expiry,
} => {
if self.duration_since_epoch() > path_absolute_expiry {
log_trace!(self.logger, "Received expired StaticInvoice path");
return Err(());
}
return Ok((recipient_id, invoice_slot));
},
_ => return Err(()),
};
}
pub fn static_invoice_persisted(&self, responder: Responder) {
let mut pending_async_payments_messages =
self.pending_async_payments_messages.lock().unwrap();
let message = AsyncPaymentsMessage::StaticInvoicePersisted(StaticInvoicePersisted {});
pending_async_payments_messages.push((message, responder.respond().into_instructions()));
}
pub fn handle_static_invoice_persisted(&self, context: AsyncPaymentsContext) -> bool {
let mut cache = self.async_receive_offer_cache.lock().unwrap();
cache.static_invoice_persisted(context)
}
pub fn writeable_async_receive_offer_cache(&self) -> Vec<u8> {
self.async_receive_offer_cache.encode()
}
}