From b3d356071947086b963b32d693b6ac94a50fd32e Mon Sep 17 00:00:00 2001 From: shaavan Date: Sun, 6 Apr 2025 19:29:07 +0530 Subject: [PATCH 1/7] Refactor: Extract `get_peers_for_blinded_path` helper Encapsulates logic for fetching peers used in blinded path creation. Reduces duplication and improves reusability across functions. --- lightning/src/ln/channelmanager.rs | 43 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 363f2ffdb65..d07b34389ad 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10862,6 +10862,23 @@ where now } + fn get_peers_for_blinded_path(&self) -> Vec { + self.per_peer_state.read().unwrap() + .iter() + .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) + .filter(|(_, peer)| peer.is_connected) + .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) + .map(|(node_id, peer)| MessageForwardNode { + node_id: *node_id, + short_channel_id: peer.channel_by_id + .iter() + .filter(|(_, channel)| channel.context().is_usable()) + .min_by_key(|(_, channel)| channel.context().channel_creation_height) + .and_then(|(_, channel)| channel.context().get_short_channel_id()), + }) + .collect::>() + } + /// Creates a collection of blinded paths by delegating to /// [`MessageRouter::create_blinded_paths`]. /// @@ -10870,13 +10887,10 @@ where let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; - let peers = self.per_peer_state.read().unwrap() - .iter() - .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) - .filter(|(_, peer)| peer.is_connected) - .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) - .map(|(node_id, _)| *node_id) - .collect::>(); + let peers = self.get_peers_for_blinded_path() + .into_iter() + .map(|node| node.node_id) + .collect(); self.message_router .create_blinded_paths(recipient, context, peers, secp_ctx) @@ -10891,20 +10905,7 @@ where let recipient = self.get_our_node_id(); let secp_ctx = &self.secp_ctx; - let peers = self.per_peer_state.read().unwrap() - .iter() - .map(|(node_id, peer_state)| (node_id, peer_state.lock().unwrap())) - .filter(|(_, peer)| peer.is_connected) - .filter(|(_, peer)| peer.latest_features.supports_onion_messages()) - .map(|(node_id, peer)| MessageForwardNode { - node_id: *node_id, - short_channel_id: peer.channel_by_id - .iter() - .filter(|(_, channel)| channel.context().is_usable()) - .min_by_key(|(_, channel)| channel.context().channel_creation_height) - .and_then(|(_, channel)| channel.context().get_short_channel_id()), - }) - .collect::>(); + let peers = self.get_peers_for_blinded_path(); self.message_router .create_compact_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx) From e7612701c9012ee5e15b4bc6a672bdbb68f01549 Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 17 Mar 2025 15:47:29 +0530 Subject: [PATCH 2/7] Introduce OffersMessageFlow --- lightning/src/offers/flow.rs | 152 +++++++++++++++++++++++++++++++++++ lightning/src/offers/mod.rs | 1 + 2 files changed, 153 insertions(+) create mode 100644 lightning/src/offers/flow.rs diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs new file mode 100644 index 00000000000..253e0cf7ccf --- /dev/null +++ b/lightning/src/offers/flow.rs @@ -0,0 +1,152 @@ +// This file is Copyright its original authors, visible in version control +// history. +// +// This file is licensed under the Apache License, Version 2.0 or the MIT license +// , at your option. +// You may not use this file except in accordance with one or both of these +// licenses. + +//! Provides data structures and functions for creating and managing Offers messages, +//! facilitating communication, and handling Bolt12 messages and payments. + +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::chain::BestBlock; +use crate::ln::inbound_payment; +use crate::onion_message::async_payments::AsyncPaymentsMessage; +use crate::onion_message::messenger::{MessageRouter, MessageSendInstructions}; +use crate::onion_message::offers::OffersMessage; +use crate::routing::router::Router; +use crate::sign::EntropySource; +use crate::sync::{Mutex, RwLock}; + +#[cfg(feature = "dnssec")] +use crate::onion_message::dns_resolution::DNSResolverMessage; + +/// A Bolt12 Offers code and flow utility provider, which facilitates utilities for +/// Bolt12 builder generation, and Onion message handling. +/// +/// [`OffersMessageFlow`] is parameterized by a number of components to achieve this. +/// +/// - [`EntropySource`] for providing random data needed for cryptographic operations +/// - [`MessageRouter`] for finding message paths when initiating and retrying onion messages +/// - [`Router`] for finding payment paths when initiating Botl12 payments. +pub struct OffersMessageFlow +where + ES::Target: EntropySource, + MR::Target: MessageRouter, + R::Target: Router, +{ + chain_hash: ChainHash, + best_block: RwLock, + + our_network_pubkey: PublicKey, + highest_seen_timestamp: AtomicUsize, + inbound_payment_key: inbound_payment::ExpandedKey, + + secp_ctx: Secp256k1, + entropy_source: ES, + + message_router: MR, + router: R, + + #[cfg(not(any(test, feature = "_test_utils")))] + pending_offers_messages: Mutex>, + #[cfg(any(test, feature = "_test_utils"))] + pub(crate) pending_offers_messages: Mutex>, + + pending_async_payments_messages: Mutex>, + + #[cfg(feature = "dnssec")] + pending_dns_onion_messages: Mutex>, +} + +impl OffersMessageFlow +where + ES::Target: EntropySource, + MR::Target: MessageRouter, + R::Target: Router, +{ + /// Creates a new [`OffersMessageFlow`] + pub fn new( + chain_hash: ChainHash, best_block: BestBlock, our_network_pubkey: PublicKey, + current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey, + entropy_source: ES, message_router: MR, router: R, + ) -> Self { + let mut secp_ctx = Secp256k1::new(); + secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + + Self { + chain_hash, + best_block: RwLock::new(best_block), + + our_network_pubkey, + highest_seen_timestamp: AtomicUsize::new(current_timestamp as usize), + inbound_payment_key, + + secp_ctx, + entropy_source, + + message_router, + router, + + pending_offers_messages: Mutex::new(Vec::new()), + pending_async_payments_messages: Mutex::new(Vec::new()), + #[cfg(feature = "dnssec")] + pending_dns_onion_messages: Mutex::new(Vec::new()), + } + } + + /// Gets the node_id held by this [`OffersMessageFlow`]` + pub fn get_our_node_id(&self) -> PublicKey { + self.our_network_pubkey + } + + 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 + } + + fn best_block_updated(&self, header: &Header) { + macro_rules! max_time { + ($timestamp: expr) => { + loop { + // Update $timestamp to be the max of its current value and the block + // timestamp. This should keep us close to the current time without relying on + // having an explicit local time source. + // Just in case we end up in a race, we loop until we either successfully + // update $timestamp or decide we don't need to. + let old_serial = $timestamp.load(Ordering::Acquire); + if old_serial >= header.time as usize { + break; + } + if $timestamp + .compare_exchange( + old_serial, + header.time as usize, + Ordering::AcqRel, + Ordering::Relaxed, + ) + .is_ok() + { + break; + } + } + }; + } + + max_time!(self.highest_seen_timestamp); + } +} diff --git a/lightning/src/offers/mod.rs b/lightning/src/offers/mod.rs index 49a95b96f86..cf078ed0e67 100644 --- a/lightning/src/offers/mod.rs +++ b/lightning/src/offers/mod.rs @@ -14,6 +14,7 @@ #[macro_use] pub mod offer; +pub mod flow; pub mod invoice; pub mod invoice_error; From 63c5624bda01bb768a9dbc9e530396113e0f9517 Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 14 Apr 2025 18:32:28 +0530 Subject: [PATCH 3/7] Define blinded path constructors in OffersMessageFlow These functions will be used in the following commit to replace closure usage in Flow trait functions. --- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/offers/flow.rs | 122 ++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index d07b34389ad..0491b8a29db 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -2805,7 +2805,7 @@ pub const MIN_CLTV_EXPIRY_DELTA: u16 = 6*8; // scale them up to suit its security policy. At the network-level, we shouldn't constrain them too much, // while avoiding to introduce a DoS vector. Further, a low CTLV_FAR_FAR_AWAY could be a source of // routing failure for any HTLC sender picking up an LDK node among the first hops. -pub(super) const CLTV_FAR_FAR_AWAY: u32 = 14 * 24 * 6; +pub(crate) const CLTV_FAR_FAR_AWAY: u32 = 14 * 24 * 6; /// Minimum CLTV difference between the current block height and received inbound payments. /// Invoices generated for payment to us must set their `min_final_cltv_expiry_delta` field to at least diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 253e0cf7ccf..965f147a5d9 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -14,18 +14,28 @@ 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::{ + BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, +}; +use crate::blinded_path::payment::{ + BlindedPaymentPath, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs, +}; +use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; use crate::chain::BestBlock; +use crate::ln::channel_state::ChannelDetails; +use crate::ln::channelmanager::{CLTV_FAR_FAR_AWAY, MAX_SHORT_LIVED_RELATIVE_EXPIRY}; use crate::ln::inbound_payment; +use crate::offers::nonce::Nonce; use crate::onion_message::async_payments::AsyncPaymentsMessage; use crate::onion_message::messenger::{MessageRouter, MessageSendInstructions}; use crate::onion_message::offers::OffersMessage; use crate::routing::router::Router; use crate::sign::EntropySource; use crate::sync::{Mutex, RwLock}; +use bitcoin::block::Header; +use bitcoin::constants::ChainHash; +use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; +use lightning_invoice::PaymentSecret; #[cfg(feature = "dnssec")] use crate::onion_message::dns_resolution::DNSResolverMessage; @@ -150,3 +160,107 @@ where max_time!(self.highest_seen_timestamp); } } + +impl OffersMessageFlow +where + ES::Target: EntropySource, + MR::Target: MessageRouter, + R::Target: Router, +{ + /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on + /// the path's intended lifetime. + /// + /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, + /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See + /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. + fn create_blinded_paths_using_absolute_expiry( + &self, context: OffersContext, absolute_expiry: Option, + peers: Vec, + ) -> Result, ()> { + let now = self.duration_since_epoch(); + let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); + + if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { + self.create_compact_blinded_paths(peers, context) + } else { + self.create_blinded_paths(peers, MessageContext::Offers(context)) + } + } + + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors. + fn create_blinded_paths( + &self, peers: Vec, context: MessageContext, + ) -> Result, ()> { + let recipient = self.get_our_node_id(); + let secp_ctx = &self.secp_ctx; + + let peers = peers.into_iter().map(|node| node.node_id).collect(); + + self.message_router + .create_blinded_paths(recipient, context, peers, secp_ctx) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) + } + + /// Creates a collection of blinded paths by delegating to + /// [`MessageRouter::create_compact_blinded_paths`]. + /// + /// Errors if the `MessageRouter` errors. + fn create_compact_blinded_paths( + &self, peers: Vec, context: OffersContext, + ) -> Result, ()> { + let recipient = self.get_our_node_id(); + let secp_ctx = &self.secp_ctx; + + let peers = peers; + + self.message_router + .create_compact_blinded_paths( + recipient, + MessageContext::Offers(context), + peers, + secp_ctx, + ) + .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) + } + + /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to + /// [`Router::create_blinded_payment_paths`]. + fn create_blinded_payment_paths( + &self, usable_channels: Vec, amount_msats: Option, + payment_secret: PaymentSecret, payment_context: PaymentContext, + relative_expiry_seconds: u32, + ) -> Result, ()> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let first_hops = usable_channels; + let payee_node_id = self.get_our_node_id(); + + // Assume shorter than usual block times to avoid spuriously failing payments too early. + 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); + + self.router.create_blinded_payment_paths( + payee_node_id, + first_hops, + payee_tlvs, + amount_msats, + secp_ctx, + ) + } +} From 6cdf7733f4b0fa56f7dbf7a9b550eec459e15d8c Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 17 Mar 2025 16:02:34 +0530 Subject: [PATCH 4/7] Implement OffersMessageFlow functions --- lightning/src/ln/channelmanager.rs | 2 +- lightning/src/ln/inbound_payment.rs | 4 +- lightning/src/offers/flow.rs | 770 +++++++++++++++++++++++++++- 3 files changed, 763 insertions(+), 13 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 0491b8a29db..62c4a91b22a 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -10317,7 +10317,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { /// Sending multiple requests increases the chances of successful delivery in case some /// paths are unavailable. However, only one invoice for a given [`PaymentId`] will be paid, /// even if multiple invoices are received. -const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; +pub const OFFERS_MESSAGE_REQUEST_LIMIT: usize = 10; impl ChannelManager where diff --git a/lightning/src/ln/inbound_payment.rs b/lightning/src/ln/inbound_payment.rs index 87781793d06..66a434d182b 100644 --- a/lightning/src/ln/inbound_payment.rs +++ b/lightning/src/ln/inbound_payment.rs @@ -194,7 +194,7 @@ pub fn create_from_hash(keys: &ExpandedKey, min_value_msat: Option, payment } #[cfg(async_payments)] -pub(super) fn create_for_spontaneous_payment( +pub(crate) fn create_for_spontaneous_payment( keys: &ExpandedKey, min_value_msat: Option, invoice_expiry_delta_secs: u32, current_time: u64, min_final_cltv_expiry_delta: Option ) -> Result { @@ -213,7 +213,7 @@ pub(super) fn create_for_spontaneous_payment( Ok(construct_payment_secret(&iv_bytes, &metadata_bytes, &keys.metadata_key)) } -pub(super) fn calculate_absolute_expiry(highest_seen_timestamp: u64, invoice_expiry_delta_secs: u32) -> u64 { +pub(crate) fn calculate_absolute_expiry(highest_seen_timestamp: u64, invoice_expiry_delta_secs: u32) -> u64 { // We assume that highest_seen_timestamp is pretty close to the current time - it's updated when // we receive a new block with the maximum time we've seen in a header. It should never be more // than two hours in the future. Thus, we add two hours here as a buffer to ensure we diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 965f147a5d9..5550713b492 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -14,31 +14,64 @@ 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::{ - BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, + AsyncPaymentsContext, BlindedMessagePath, MessageContext, MessageForwardNode, OffersContext, }; use crate::blinded_path::payment::{ - BlindedPaymentPath, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs, + BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, + PaymentContext, UnauthenticatedReceiveTlvs, }; use crate::chain::channelmonitor::LATENCY_GRACE_PERIOD_BLOCKS; use crate::chain::BestBlock; use crate::ln::channel_state::ChannelDetails; -use crate::ln::channelmanager::{CLTV_FAR_FAR_AWAY, MAX_SHORT_LIVED_RELATIVE_EXPIRY}; +use crate::ln::channelmanager::{ + Verification, OFFERS_MESSAGE_REQUEST_LIMIT, + {PaymentId, CLTV_FAR_FAR_AWAY, MAX_SHORT_LIVED_RELATIVE_EXPIRY}, +}; use crate::ln::inbound_payment; +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::{DerivedMetadata, Offer, OfferBuilder}; +use crate::offers::parse::Bolt12SemanticError; +use crate::offers::refund::{Refund, RefundBuilder}; use crate::onion_message::async_payments::AsyncPaymentsMessage; -use crate::onion_message::messenger::{MessageRouter, MessageSendInstructions}; +use crate::onion_message::dns_resolution::HumanReadableName; +use crate::onion_message::messenger::{Destination, MessageRouter, MessageSendInstructions}; use crate::onion_message::offers::OffersMessage; +use crate::onion_message::packet::OnionMessageContents; use crate::routing::router::Router; -use crate::sign::EntropySource; +use crate::sign::{EntropySource, NodeSigner}; use crate::sync::{Mutex, RwLock}; -use bitcoin::block::Header; -use bitcoin::constants::ChainHash; -use bitcoin::secp256k1::{self, PublicKey, Secp256k1}; -use lightning_invoice::PaymentSecret; +use crate::types::payment::{PaymentHash, PaymentSecret}; + +#[cfg(async_payments)] +use { + crate::blinded_path::payment::AsyncBolt12OfferContext, + crate::offers::offer::Amount, + crate::offers::signer, + crate::offers::static_invoice::{ + StaticInvoice, StaticInvoiceBuilder, + DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, + }, + crate::onion_message::async_payments::HeldHtlcAvailable, +}; #[cfg(feature = "dnssec")] -use crate::onion_message::dns_resolution::DNSResolverMessage; +use { + crate::blinded_path::message::DNSResolverContext, + crate::onion_message::dns_resolution::{DNSResolverMessage, DNSSECQuery}, +}; /// A Bolt12 Offers code and flow utility provider, which facilitates utilities for /// Bolt12 builder generation, and Onion message handling. @@ -264,3 +297,720 @@ where ) } } + +fn enqueue_onion_message_with_reply_paths( + message: T, message_paths: &[BlindedMessagePath], reply_paths: Vec, + 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)); + }); +} + +impl OffersMessageFlow +where + ES::Target: EntropySource, + MR::Target: MessageRouter, + R::Target: Router, +{ + /// Verifies an [`InvoiceRequest`] using the provided [`OffersContext`] or the invoice request's own metadata. + /// + /// - If an [`OffersContext::InvoiceRequest`] with a `nonce` is provided, verification is performed using recipient context data. + /// - If no context is provided but the [`InvoiceRequest`] contains metadata, verification is performed using that metadata. + /// - If neither is available, verification fails. + /// + /// # Errors + /// + /// Returns an error if: + /// - Both `OffersContext` and `InvoiceRequest` metadata are absent or invalid. + /// - The verification process (via recipient context data or metadata) fails. + pub fn verify_invoice_request( + &self, invoice_request: InvoiceRequest, context: Option, + ) -> Result { + 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), + _ => return Err(()), + }; + + let invoice_request = match nonce { + Some(nonce) => { + match invoice_request.verify_using_recipient_data(nonce, expanded_key, secp_ctx) { + Ok(invoice_request) => invoice_request, + Err(()) => return Err(()), + } + }, + None => match invoice_request.verify_using_metadata(expanded_key, secp_ctx) { + Ok(invoice_request) => invoice_request, + Err(()) => return Err(()), + }, + }; + + Ok(invoice_request) + } + + /// Verifies a [`Bolt12Invoice`] using the provided [`OffersContext`] or the invoice's own metadata, + /// returning the corresponding [`PaymentId`] if successful. + /// + /// - If an [`OffersContext::OutboundPayment`] with a `nonce` is provided, verification is performed + /// using the payer's context data. + /// - If no context is provided and the invoice corresponds to a [`Refund`] without blinded paths, + /// verification is performed using the invoice's metadata. + /// - If neither condition is met, verification fails. + pub fn verify_bolt12_invoice( + &self, invoice: &Bolt12Invoice, context: Option<&OffersContext>, + ) -> Result { + 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(()), + } + } + + /// Verifies the provided [`AsyncPaymentsContext`] for an [`AsyncPaymentsMessage`]. + /// + /// Depending on whether the context is for an inbound or outbound payment, the function + /// performs verification using nonces and HMAC values, stored within the context. + /// + /// - For **Inbound Payments**, the context is verified using the `nonce` and `hmac` values, + /// and ensures that the context has not expired based on `path_absolute_expiry`. + /// - For **Outbound Payments**, the context is verified using the `nonce` and `hmac` values, + /// and if valid, returns the associated [`PaymentId`]. + /// + /// # Errors + /// + /// Returns `Err(())` if: + /// - The HMAC verification fails for either inbound or outbound context. + /// - The inbound payment context has expired. + /// + /// [`AsyncPaymentsMessage`]: crate::onion_message::async_payments::AsyncPaymentsMessage + #[cfg(async_payments)] + pub fn verify_async_context( + &self, context: AsyncPaymentsContext, + ) -> Result, ()> { + match context { + AsyncPaymentsContext::InboundPayment { nonce, hmac, path_absolute_expiry } => { + signer::verify_held_htlc_available_context(nonce, hmac, &self.inbound_payment_key)?; + + if self.duration_since_epoch() > path_absolute_expiry { + return Err(()); + } + Ok(None) + }, + AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } => { + payment_id.verify_for_async_payment(hmac, nonce, &self.inbound_payment_key)?; + Ok(Some(payment_id)) + }, + } + } + + /// Creates an [`OfferBuilder`] such that the [`Offer`] it builds is recognized by the + /// [`ChannelManager`] when handling [`InvoiceRequest`] messages for the offer. The offer's + /// expiration will be `absolute_expiry` if `Some`, otherwise it will not expire. + /// + /// # Privacy + /// + /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the offer based on the given + /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for + /// privacy implications, as well as those of the parameterized [`Router`], which implements + /// [`MessageRouter`]. + /// + /// Also uses a derived signing pubkey in the offer for recipient privacy. + /// + /// # Limitations + /// + /// Requires a direct connection to the introduction node in the responding [`InvoiceRequest`]'s + /// reply path. + /// + /// # Errors + /// + /// Returns an error if the parameterized [`Router`] is unable to create a blinded path for the offer. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + pub fn create_offer_builder( + &self, absolute_expiry: Option, nonce: Option, + peers: Vec, + ) -> Result, Bolt12SemanticError> { + let node_id = self.get_our_node_id(); + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let nonce = nonce.unwrap_or(Nonce::from_entropy_source(entropy)); + let context = OffersContext::InvoiceRequest { nonce }; + + let path = self + .create_blinded_paths_using_absolute_expiry(context, absolute_expiry, peers) + .and_then(|paths| paths.into_iter().next().ok_or(())) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) + .chain_hash(self.chain_hash) + .path(path); + + let builder = match absolute_expiry { + None => builder, + Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), + }; + + Ok(builder) + } + + /// Creates a [`RefundBuilder`] such that the [`Refund`] it builds is recognized by the + /// [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the refund. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the refund. + /// See [Avoiding Duplicate Payments] for additional requirements once the payment has been sent. + /// + /// The builder will have the provided expiration set. Any changes to the expiration on the + /// returned builder will not be honored by [`ChannelManager`]. For non-`std`, the highest seen + /// block time minus two hours is used for the current time when determining if the refund has + /// expired. + /// + /// To revoke the refund, use [`ChannelManager::abandon_payment`] prior to receiving the + /// invoice. If abandoned, or if an invoice is not received before expiration, the payment will fail + /// with an [`Event::PaymentFailed`]. + /// + /// If `max_total_routing_fee_msat` is not specified, the default from + /// [`RouteParameters::from_payment_params_and_value`] is applied. + /// + /// # Privacy + /// + /// Uses [`MessageRouter`] to construct a [`BlindedMessagePath`] for the refund based on the given + /// `absolute_expiry` according to [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. See those docs for + /// privacy implications. + /// + /// Also uses a derived payer id in the refund for payer privacy. + /// + /// # Limitations + /// + /// Requires a direct connection to an introduction node in the responding + /// [`Bolt12Invoice::payment_paths`]. + /// + /// # Errors + /// + /// Returns an error if: + /// - A duplicate `payment_id` is provided, given the caveats in the aforementioned link. + /// - `amount_msats` is invalid, or + /// - The parameterized [`Router`] is unable to create a blinded path for the refund. + /// + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + pub fn create_refund_builder( + &self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, + peers: Vec, + ) -> Result, Bolt12SemanticError> { + let node_id = self.get_our_node_id(); + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + let secp_ctx = &self.secp_ctx; + + let nonce = Nonce::from_entropy_source(entropy); + let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; + + let path = self + .create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry), peers) + .and_then(|paths| paths.into_iter().next().ok_or(())) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + let 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) + .path(path); + + Ok(builder) + } + + /// Creates an [`InvoiceRequestBuilder`] such that the [`InvoiceRequest`] it builds is recognized + /// by the [`ChannelManager`] when handling [`Bolt12Invoice`] messages for the invoice request. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the invoice request. + /// See [Avoiding Duplicate Payments] for additional requirements once the payment has been sent. + /// + /// # Nonce + /// The nonce is used to create a unique [`InvoiceRequest::payer_metadata`] for the invoice request. + /// These will be used to verify the corresponding [`Bolt12Invoice`] when it is received. + /// + /// [Avoiding Duplicate Payments]: #avoiding-duplicate-payments + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + pub fn create_invoice_request_builder<'a>( + &'a self, offer: &'a Offer, nonce: Nonce, quantity: Option, amount_msats: Option, + payer_note: Option, human_readable_name: Option, + payment_id: PaymentId, + ) -> Result, Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let secp_ctx = &self.secp_ctx; + + let builder: InvoiceRequestBuilder = + offer.request_invoice(expanded_key, nonce, secp_ctx, payment_id)?.into(); + let builder = builder.chain_hash(self.chain_hash)?; + + let builder = match quantity { + None => builder, + Some(quantity) => builder.quantity(quantity)?, + }; + let builder = match amount_msats { + None => builder, + Some(amount_msats) => builder.amount_msats(amount_msats)?, + }; + let builder = match payer_note { + None => builder, + Some(payer_note) => builder.payer_note(payer_note), + }; + let builder = match human_readable_name { + None => builder, + Some(hrn) => builder.sourced_from_human_readable_name(hrn), + }; + + Ok(builder) + } + + /// Creates a [`StaticInvoiceBuilder`] such that the [`StaticInvoice`] it builds is recognized + /// by the [`ChannelManager`]. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + #[cfg(async_payments)] + pub fn create_static_invoice_builder<'a>( + &'a self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option, + usable_channels: Vec, peers: Vec, + ) -> Result, Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.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 relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); + let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); + + let created_at = self.duration_since_epoch(); + let payment_secret = inbound_payment::create_for_spontaneous_payment( + &self.inbound_payment_key, + amount_msat, + relative_expiry_secs, + created_at.as_secs(), + None, + ) + .map_err(|()| Bolt12SemanticError::InvalidAmount)?; + + let payment_paths = self + .create_blinded_payment_paths( + usable_channels, + amount_msat, + payment_secret, + payment_context, + relative_expiry_secs, + ) + .map_err(|()| Bolt12SemanticError::MissingPaths)?; + + let nonce = Nonce::from_entropy_source(entropy); + let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key); + 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 { + nonce, + hmac, + 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)) + } + + /// Creates an [`InvoiceBuilder`] using the provided [`Refund`] such that the + /// [`InvoiceBuilder`] it builds is recognized by the [`ChannelManager`]. + /// + /// [`ChannelManager`]: crate::ln::channelmanager::ChannelManager + pub fn create_invoice_builder_from_refund<'a>( + &'a self, refund: &'a Refund, payment_hash: PaymentHash, payment_secret: PaymentSecret, + usable_channels: Vec, + ) -> Result, Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.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( + 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) + } + + /// Creates a response for the provided [`VerifiedInvoiceRequest`]. + /// + /// A response can be either an [`OffersMessage::Invoice`] with additional [`MessageContext`], + /// or an [`OffersMessage::InvoiceError`], depending on the [`InvoiceRequest`]. + /// + /// An [`OffersMessage::InvoiceError`] will be generated if: + /// - We fail to generate valid payment paths to include in the [`Bolt12Invoice`]. + /// - We fail to generate a valid signed [`Bolt12Invoice`] for the [`InvoiceRequest`]. + pub fn create_response_for_invoice_request( + &self, signer: &NS, invoice_request: VerifiedInvoiceRequest, amount_msats: u64, + payment_hash: PaymentHash, payment_secret: PaymentSecret, + usable_channels: Vec, + ) -> (OffersMessage, Option) + where + NS::Target: NodeSigner, + { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.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( + 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::::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::::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 nonce = Nonce::from_entropy_source(entropy); + let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); + let context = MessageContext::Offers(OffersContext::InboundPayment { + payment_hash, + nonce, + hmac, + }); + + (OffersMessage::Invoice(invoice), Some(context)) + }, + Err(error) => (OffersMessage::InvoiceError(error.into()), None), + } + } + + /// Enqueues the created [`InvoiceRequest`] to be sent to the counterparty. + /// + /// # Payment + /// + /// The provided `payment_id` is used to ensure that only one invoice is paid for the invoice request. + /// + /// # Nonce + /// The nonce is used to create a unique [`MessageContext`] for the reply paths. + /// These will be used to verify the corresponding [`Bolt12Invoice`] when it is received. + /// + /// Note: The provided [`Nonce`] MUST be the same as the [`Nonce`] used for creating the + /// [`InvoiceRequest`] to ensure correct verification of the corresponding [`Bolt12Invoice`]. + /// + /// See [`OffersMessageFlow::create_invoice_request_builder`] for more details. + /// + /// # Peers + /// + /// The user must provide a list of [`MessageForwardNode`] that will be used to generate valid + /// reply paths for the counterparty to send back the corresponding [`Bolt12Invoice`] or [`InvoiceError`]. + pub fn enqueue_invoice_request( + &self, invoice_request: InvoiceRequest, payment_id: PaymentId, nonce: Option, + peers: Vec, + ) -> Result<(), Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + + let nonce = nonce.unwrap_or(Nonce::from_entropy_source(entropy)); + + let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key); + let context = MessageContext::Offers(OffersContext::OutboundPayment { + payment_id, + nonce, + hmac: Some(hmac), + }); + 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(()) + } + + /// Enqueues the created [`Bolt12Invoice`] corresponding to a [`Refund`] to be sent + /// to the counterparty. + /// + /// # Peers + /// + /// The user must provide a list of [`MessageForwardNode`] that will be used to generate valid + /// reply paths for the counterparty to send back the corresponding [`InvoiceError`] in case the + /// user fails to pay the [`Bolt12Invoice`]. + pub fn enqueue_invoice( + &self, invoice: Bolt12Invoice, refund: &Refund, payment_hash: PaymentHash, + peers: Vec, + ) -> Result<(), Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + + let nonce = Nonce::from_entropy_source(entropy); + let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); + let context = MessageContext::Offers(OffersContext::InboundPayment { + payment_hash: invoice.payment_hash(), + nonce, + hmac, + }); + + 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.clone()); + enqueue_onion_message_with_reply_paths( + message, + refund.paths(), + reply_paths, + &mut pending_offers_messages, + ); + } + + Ok(()) + } + + /// Enqueues the created [`StaticInvoice`] to be sent to the counterparty. + /// + /// # Peers + /// + /// The user must provide a list of [`MessageForwardNode`] that will be used to generate valid + /// reply paths for the counterparty to send back the corresponding [`InvoiceError`] in case the + /// user fails to pay the [`StaticInvoice`]. + #[cfg(async_payments)] + pub fn enqueue_async_payment_messages( + &self, invoice: &StaticInvoice, payment_id: PaymentId, peers: Vec, + ) -> Result<(), Bolt12SemanticError> { + let expanded_key = &self.inbound_payment_key; + let entropy = &*self.entropy_source; + + let nonce = Nonce::from_entropy_source(entropy); + let hmac = payment_id.hmac_for_async_payment(nonce, expanded_key); + let context = MessageContext::AsyncPayments(AsyncPaymentsContext::OutboundPayment { + payment_id, + nonce, + hmac, + }); + + let reply_paths = self + .create_blinded_paths(peers, context) + .map_err(|_| Bolt12SemanticError::MissingPaths)?; + + 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(()) + } + + /// Enqueues the created [`DNSSECQuery`] to be sent to the counterparty. + /// + /// # Peers + /// + /// The user must provide a list of [`MessageForwardNode`] that will be used to generate valid + /// reply paths for the counterparty to send back the corresponding response for the [`DNSSECQuery`] + /// message. + #[cfg(feature = "dnssec")] + pub fn enqueue_dns_onion_message( + &self, message: DNSSECQuery, context: DNSResolverContext, dns_resolvers: Vec, + peers: Vec, + ) -> 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(()) + } + + /// Gets the enqueued [`OffersMessage`] with their corresponding [`MessageSendInstructions`]. + pub fn get_and_clear_pending_offers_messages( + &self, + ) -> Vec<(OffersMessage, MessageSendInstructions)> { + core::mem::take(&mut self.pending_offers_messages.lock().unwrap()) + } + + /// Gets the enqueued [`AsyncPaymentsMessage`] with their corresponding [`MessageSendInstructions`]. + pub fn get_and_clear_pending_async_messages( + &self, + ) -> Vec<(AsyncPaymentsMessage, MessageSendInstructions)> { + core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap()) + } + + /// Gets the enqueued [`DNSResolverMessage`] with their corresponding [`MessageSendInstructions`]. + #[cfg(feature = "dnssec")] + pub fn get_and_clear_pending_dns_messages( + &self, + ) -> Vec<(DNSResolverMessage, MessageSendInstructions)> { + core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap()) + } +} From 2bf4c545e0483fde40dc581e5508aec8426ecc86 Mon Sep 17 00:00:00 2001 From: shaavan Date: Wed, 16 Apr 2025 18:46:44 +0530 Subject: [PATCH 5/7] Introduce Flow parameter in ChannelManager --- lightning-background-processor/src/lib.rs | 24 +++++++++++++++--- lightning-liquidity/tests/common/mod.rs | 22 ++++++++++++++--- lightning/src/ln/channelmanager.rs | 30 +++++++++++++++++------ lightning/src/ln/functional_test_utils.rs | 16 ++++++++++-- lightning/src/offers/flow.rs | 2 +- 5 files changed, 77 insertions(+), 17 deletions(-) diff --git a/lightning-background-processor/src/lib.rs b/lightning-background-processor/src/lib.rs index 46d990bb37e..af63b2316d4 100644 --- a/lightning-background-processor/src/lib.rs +++ b/lightning-background-processor/src/lib.rs @@ -1081,11 +1081,14 @@ mod tests { IgnoringMessageHandler, MessageHandler, PeerManager, SocketDescriptor, }; use lightning::ln::types::ChannelId; + use lightning::offers::flow::OffersMessageFlow; use lightning::onion_message::messenger::{DefaultMessageRouter, OnionMessenger}; use lightning::routing::gossip::{NetworkGraph, P2PGossipSync}; use lightning::routing::router::{CandidateRouteHop, DefaultRouter, Path, RouteHop}; use lightning::routing::scoring::{ChannelUsage, LockableScore, ScoreLookUp, ScoreUpdate}; - use lightning::sign::{ChangeDestinationSource, InMemorySigner, KeysManager}; + use lightning::sign::{ + ChangeDestinationSource, InMemorySigner, KeysManager, NodeSigner, Recipient, + }; use lightning::types::features::{ChannelFeatures, NodeFeatures}; use lightning::types::payment::PaymentHash; use lightning::util::config::UserConfig; @@ -1557,6 +1560,21 @@ mod tests { network_graph.clone(), Arc::clone(&keys_manager), )); + let best_block = BestBlock::from_network(network); + let params = ChainParameters { network, best_block }; + let chain_hash = ChainHash::using_genesis_block(params.network); + + let flow = OffersMessageFlow::new( + chain_hash, + params.best_block, + keys_manager.get_node_id(Recipient::Node).unwrap(), + genesis_block.header.time, + keys_manager.get_inbound_payment_key(), + keys_manager.clone(), + msg_router.clone(), + router.clone(), + ); + let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin)); let kv_store = Arc::new(FilesystemStore::new(format!("{}_persister_{}", &persist_dir, i).into())); @@ -1569,14 +1587,14 @@ mod tests { fee_estimator.clone(), kv_store.clone(), )); - let best_block = BestBlock::from_network(network); - let params = ChainParameters { network, best_block }; + let manager = Arc::new(ChannelManager::new( fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), msg_router.clone(), + flow, logger.clone(), keys_manager.clone(), keys_manager.clone(), diff --git a/lightning-liquidity/tests/common/mod.rs b/lightning-liquidity/tests/common/mod.rs index f114f7b9c89..1ad60b47f89 100644 --- a/lightning-liquidity/tests/common/mod.rs +++ b/lightning-liquidity/tests/common/mod.rs @@ -5,7 +5,8 @@ #![allow(unused_macros)] use lightning::chain::Filter; -use lightning::sign::EntropySource; +use lightning::offers::flow::OffersMessageFlow; +use lightning::sign::{EntropySource, NodeSigner, Recipient}; use bitcoin::blockdata::constants::{genesis_block, ChainHash}; use bitcoin::blockdata::transaction::Transaction; @@ -421,6 +422,22 @@ pub(crate) fn create_liquidity_node( )); let msg_router = Arc::new(DefaultMessageRouter::new(Arc::clone(&network_graph), Arc::clone(&keys_manager))); + + let best_block = BestBlock::from_network(network); + let chain_params = ChainParameters { network, best_block }; + let chain_hash = ChainHash::using_genesis_block(chain_params.network); + + let flow = OffersMessageFlow::new( + chain_hash, + chain_params.best_block, + keys_manager.get_node_id(Recipient::Node).unwrap(), + genesis_block.header.time, + keys_manager.get_inbound_payment_key(), + keys_manager.clone(), + msg_router.clone(), + router.clone(), + ); + let chain_source = Arc::new(test_utils::TestChainSource::new(Network::Bitcoin)); let kv_store = Arc::new(FilesystemStore::new(format!("{}_persister_{}", &persist_dir, i).into())); @@ -431,14 +448,13 @@ pub(crate) fn create_liquidity_node( fee_estimator.clone(), kv_store.clone(), )); - let best_block = BestBlock::from_network(network); - let chain_params = ChainParameters { network, best_block }; let channel_manager = Arc::new(ChannelManager::new( fee_estimator.clone(), chain_monitor.clone(), tx_broadcaster.clone(), router.clone(), msg_router.clone(), + flow, logger.clone(), keys_manager.clone(), keys_manager.clone(), diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index 62c4a91b22a..ae72b807e6e 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -49,6 +49,7 @@ use crate::events::{self, Event, EventHandler, EventsProvider, InboundChannelFun // construct one themselves. use crate::ln::inbound_payment; use crate::ln::types::ChannelId; +use crate::offers::flow::OffersMessageFlow; use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret}; use crate::ln::channel::{self, Channel, ChannelError, ChannelUpdateStatus, FundedChannel, ShutdownResult, UpdateFulfillCommitFetch, OutboundV1Channel, ReconnectionMsg, InboundV1Channel, WithChannelContext}; use crate::ln::channel::PendingV2Channel; @@ -1718,6 +1719,7 @@ where /// # tx_broadcaster: &dyn lightning::chain::chaininterface::BroadcasterInterface, /// # router: &lightning::routing::router::DefaultRouter<&NetworkGraph<&'a L>, &'a L, &ES, &S, SP, SL>, /// # message_router: &lightning::onion_message::messenger::DefaultMessageRouter<&NetworkGraph<&'a L>, &'a L, &ES>, +/// flow: lightning::offers::flow::OffersMessageFlow<&ES,&lightning::onion_message::messenger::DefaultMessageRouter<&NetworkGraph<&'a L>, &'a L, &ES>, &lightning::routing::router::DefaultRouter<&NetworkGraph<&'a L>, &'a L, &ES, &S, SP, SL>>, /// # logger: &L, /// # entropy_source: &ES, /// # node_signer: &dyn lightning::sign::NodeSigner, @@ -1733,7 +1735,7 @@ where /// }; /// let default_config = UserConfig::default(); /// let channel_manager = ChannelManager::new( -/// fee_estimator, chain_monitor, tx_broadcaster, router, message_router, logger, +/// fee_estimator, chain_monitor, tx_broadcaster, router, message_router, flow, logger, /// entropy_source, node_signer, signer_provider, default_config.clone(), params, current_timestamp, /// ); /// @@ -2445,6 +2447,11 @@ where router: R, message_router: MR, + #[cfg(test)] + pub(super) flow: OffersMessageFlow, + #[cfg(not(test))] + flow: OffersMessageFlow, + /// See `ChannelManager` struct-level documentation for lock order requirements. #[cfg(any(test, feature = "_test_utils"))] pub(super) best_block: RwLock, @@ -3540,8 +3547,8 @@ where /// [`block_disconnected`]: chain::Listen::block_disconnected /// [`params.best_block.block_hash`]: chain::BestBlock::block_hash pub fn new( - fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, logger: L, - entropy_source: ES, node_signer: NS, signer_provider: SP, config: UserConfig, + fee_est: F, chain_monitor: M, tx_broadcaster: T, router: R, message_router: MR, flow: OffersMessageFlow, + logger: L, entropy_source: ES, node_signer: NS, signer_provider: SP, config: UserConfig, params: ChainParameters, current_timestamp: u32, ) -> Self { let mut secp_ctx = Secp256k1::new(); @@ -3555,6 +3562,7 @@ where tx_broadcaster, router, message_router, + flow, best_block: RwLock::new(params.best_block), @@ -11499,6 +11507,7 @@ where self.transactions_confirmed(header, txdata, height); self.best_block_updated(header, height); + self.flow.best_block_updated(header); } fn block_disconnected(&self, header: &Header, height: u32) { @@ -13602,7 +13611,7 @@ impl Readable for VecDeque<(Event, Option)> { /// which you've already broadcasted the transaction. /// /// [`ChainMonitor`]: crate::chain::chainmonitor::ChainMonitor -pub struct ChannelManagerReadArgs<'a, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref> +pub struct ChannelManagerReadArgs<'a, M: Deref, T: Deref, ES: Deref + Clone, NS: Deref, SP: Deref, F: Deref, R: Deref + Clone, MR: Deref + Clone, L: Deref> where M::Target: chain::Watch<::EcdsaSigner>, T::Target: BroadcasterInterface, @@ -13648,6 +13657,7 @@ where /// The [`MessageRouter`] used for constructing [`BlindedMessagePath`]s for [`Offer`]s, /// [`Refund`]s, and any reply paths. pub message_router: MR, + /// The Logger for use in the ChannelManager and which may be used to log information during /// deserialization. pub logger: L, @@ -13669,7 +13679,7 @@ where pub channel_monitors: HashMap::EcdsaSigner>>, } -impl<'a, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref> +impl<'a, M: Deref, T: Deref, ES: Deref + Clone, NS: Deref, SP: Deref, F: Deref, R: Deref + Clone, MR: Deref + Clone, L: Deref> ChannelManagerReadArgs<'a, M, T, ES, NS, SP, F, R, MR, L> where M::Target: chain::Watch<::EcdsaSigner>, @@ -13703,7 +13713,7 @@ where // Implement ReadableArgs for an Arc'd ChannelManager to make it a bit easier to work with the // SipmleArcChannelManager type: -impl<'a, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref> +impl<'a, M: Deref, T: Deref, ES: Deref + Clone, NS: Deref, SP: Deref, F: Deref, R: Deref + Clone, MR: Deref + Clone, L: Deref> ReadableArgs> for (BlockHash, Arc>) where M::Target: chain::Watch<::EcdsaSigner>, @@ -13722,7 +13732,7 @@ where } } -impl<'a, M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref> +impl<'a, M: Deref, T: Deref, ES: Deref + Clone, NS: Deref, SP: Deref, F: Deref, R: Deref + Clone, MR: Deref + Clone, L: Deref> ReadableArgs> for (BlockHash, ChannelManager) where M::Target: chain::Watch<::EcdsaSigner>, @@ -14704,6 +14714,9 @@ where } } + let best_block = BestBlock::new(best_block_hash, best_block_height); + let flow = OffersMessageFlow::new(chain_hash, best_block, our_network_pubkey, highest_seen_timestamp, expanded_inbound_key, args.entropy_source.clone(), args.message_router.clone(), args.router.clone()); + let channel_manager = ChannelManager { chain_hash, fee_estimator: bounded_fee_estimator, @@ -14711,8 +14724,9 @@ where tx_broadcaster: args.tx_broadcaster, router: args.router, message_router: args.message_router, + flow, - best_block: RwLock::new(BestBlock::new(best_block_hash, best_block_height)), + best_block: RwLock::new(best_block), inbound_payment_key: expanded_inbound_key, pending_outbound_payments: pending_outbounds, diff --git a/lightning/src/ln/functional_test_utils.rs b/lightning/src/ln/functional_test_utils.rs index 45336543483..d61dbc3cb58 100644 --- a/lightning/src/ln/functional_test_utils.rs +++ b/lightning/src/ln/functional_test_utils.rs @@ -16,6 +16,7 @@ use crate::chain::transaction::OutPoint; use crate::events::{ClaimedHTLC, ClosureReason, Event, HTLCDestination, PaidBolt12Invoice, PathFailure, PaymentFailureReason, PaymentPurpose}; use crate::events::bump_transaction::{BumpTransactionEvent, BumpTransactionEventHandler, Wallet, WalletSource}; use crate::ln::types::ChannelId; +use crate::offers::flow::OffersMessageFlow; use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret}; use crate::ln::channelmanager::{AChannelManager, ChainParameters, ChannelManager, ChannelManagerReadArgs, RAACommitmentOrder, RecipientOnionFields, PaymentId, MIN_CLTV_EXPIRY_DELTA}; use crate::types::features::InitFeatures; @@ -27,7 +28,7 @@ use crate::onion_message::messenger::OnionMessenger; use crate::ln::onion_utils::LocalHTLCFailureReason; use crate::routing::gossip::{P2PGossipSync, NetworkGraph, NetworkUpdate}; use crate::routing::router::{self, PaymentParameters, Route, RouteParameters}; -use crate::sign::{EntropySource, RandomBytes}; +use crate::sign::{EntropySource, NodeSigner, RandomBytes, Recipient}; use crate::util::config::{MaxDustHTLCExposure, UserConfig}; use crate::util::logger::Logger; use crate::util::scid_utils; @@ -37,6 +38,7 @@ use crate::util::test_utils; use crate::util::test_utils::{TestChainMonitor, TestScorer, TestKeysInterface}; use crate::util::ser::{ReadableArgs, Writeable}; +use bitcoin::constants::ChainHash; use bitcoin::{Weight, WPubkeyHash}; use bitcoin::amount::Amount; use bitcoin::block::{Block, Header, Version as BlockVersion}; @@ -3383,7 +3385,17 @@ pub fn create_node_chanmgrs<'a, 'b>(node_count: usize, cfgs: &'a Vec network, best_block: BestBlock::from_network(network), }; - let node = ChannelManager::new(cfgs[i].fee_estimator, &cfgs[i].chain_monitor, cfgs[i].tx_broadcaster, &cfgs[i].router, &cfgs[i].message_router, cfgs[i].logger, cfgs[i].keys_manager, + let chain_hash = ChainHash::using_genesis_block(params.network); + let our_network_pubkey = cfgs[i].keys_manager.get_node_id(Recipient::Node).unwrap(); + let expanded_inbound_key = cfgs[i].keys_manager.get_inbound_payment_key(); + + let flow = OffersMessageFlow::new( + chain_hash, params.best_block, our_network_pubkey, + genesis_block.header.time, expanded_inbound_key, + cfgs[i].keys_manager, &cfgs[i].message_router, &cfgs[i].router + ); + + let node = ChannelManager::new(cfgs[i].fee_estimator, &cfgs[i].chain_monitor, cfgs[i].tx_broadcaster, &cfgs[i].router, &cfgs[i].message_router, flow, cfgs[i].logger, cfgs[i].keys_manager, cfgs[i].keys_manager, cfgs[i].keys_manager, if node_config[i].is_some() { node_config[i].clone().unwrap() } else { test_default_channel_config() }, params, genesis_block.header.time); chanmgrs.push(node); } diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 5550713b492..0955ff0c332 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -162,7 +162,7 @@ where now } - fn best_block_updated(&self, header: &Header) { + pub(crate) fn best_block_updated(&self, header: &Header) { macro_rules! max_time { ($timestamp: expr) => { loop { From 5d4ceec8a311c778b2d5232662f7e4c91ab6920c Mon Sep 17 00:00:00 2001 From: shaavan Date: Thu, 24 Apr 2025 15:07:19 +0530 Subject: [PATCH 6/7] f: Temporary test failure fixup --- lightning/src/offers/flow.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lightning/src/offers/flow.rs b/lightning/src/offers/flow.rs index 0955ff0c332..7ff55b671f4 100644 --- a/lightning/src/offers/flow.rs +++ b/lightning/src/offers/flow.rs @@ -123,8 +123,10 @@ where current_timestamp: u32, inbound_payment_key: inbound_payment::ExpandedKey, entropy_source: ES, message_router: MR, router: R, ) -> Self { - let mut secp_ctx = Secp256k1::new(); - secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); + let secp_ctx = Secp256k1::new(); + // Note: Temporarily disabling entropy source during construction, + // as seeded_randomize causes a test failure. + // secp_ctx.seeded_randomize(&entropy_source.get_secure_random_bytes()); Self { chain_hash, From 0d0ca37a854fe0de348ffe08a81d74c9540af066 Mon Sep 17 00:00:00 2001 From: shaavan Date: Mon, 17 Mar 2025 18:01:30 +0530 Subject: [PATCH 7/7] Introduce Flow usage in ChannelManager --- lightning/src/ln/channelmanager.rs | 485 ++++------------------------- lightning/src/ln/offers_tests.rs | 14 +- 2 files changed, 60 insertions(+), 439 deletions(-) diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs index ae72b807e6e..255fb6b14a9 100644 --- a/lightning/src/ln/channelmanager.rs +++ b/lightning/src/ln/channelmanager.rs @@ -35,10 +35,10 @@ use bitcoin::{secp256k1, Sequence}; use bitcoin::{TxIn, Weight}; use crate::events::FundingInfo; -use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext}; +use crate::blinded_path::message::{AsyncPaymentsContext, OffersContext}; use crate::blinded_path::NodeIdLookUp; use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode}; -use crate::blinded_path::payment::{AsyncBolt12OfferContext, BlindedPaymentPath, Bolt12OfferContext, Bolt12RefundContext, PaymentConstraints, PaymentContext, UnauthenticatedReceiveTlvs}; +use crate::blinded_path::payment::{AsyncBolt12OfferContext, Bolt12OfferContext, PaymentContext, UnauthenticatedReceiveTlvs}; use crate::chain; use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock}; use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator}; @@ -66,9 +66,9 @@ use crate::ln::msgs::{BaseMessageHandler, ChannelMessageHandler, CommitmentUpdat #[cfg(test)] use crate::ln::outbound_payment; use crate::ln::outbound_payment::{Bolt11PaymentError, OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration}; -use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, ExplicitSigningPubkey, InvoiceBuilder, UnsignedBolt12Invoice}; +use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder}; use crate::offers::invoice_error::InvoiceError; -use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder}; +use crate::offers::invoice_request::InvoiceRequest; use crate::offers::nonce::Nonce; use crate::offers::offer::{Offer, OfferBuilder}; use crate::offers::parse::Bolt12SemanticError; @@ -89,11 +89,10 @@ use crate::util::ser::{BigSize, FixedLengthReader, LengthReadable, Readable, Rea use crate::util::logger::{Level, Logger, WithContext}; use crate::util::errors::APIError; -#[cfg(async_payments)] use { - crate::offers::offer::Amount, - crate::offers::static_invoice::{DEFAULT_RELATIVE_EXPIRY as STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY, StaticInvoice, StaticInvoiceBuilder}, -}; - +#[cfg(async_payments)] +use crate::offers::static_invoice::{StaticInvoice, StaticInvoiceBuilder}; +#[cfg(all(test, async_payments))] +use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints}; #[cfg(feature = "dnssec")] use crate::blinded_path::message::DNSResolverContext; #[cfg(feature = "dnssec")] @@ -2627,12 +2626,6 @@ where event_persist_notifier: Notifier, needs_persist_flag: AtomicBool, - #[cfg(not(any(test, feature = "_test_utils")))] - pending_offers_messages: Mutex>, - #[cfg(any(test, feature = "_test_utils"))] - pub(crate) pending_offers_messages: Mutex>, - pending_async_payments_messages: Mutex>, - /// Tracks the message events that are to be broadcasted when we are connected to some peer. pending_broadcast_messages: Mutex>, @@ -2653,9 +2646,6 @@ where #[cfg(feature = "dnssec")] hrn_resolver: OMNameResolver, - #[cfg(feature = "dnssec")] - pending_dns_onion_messages: Mutex>, - #[cfg(feature = "_test_utils")] /// In testing, it is useful be able to forge a name -> offer mapping so that we can pay an /// offer generated in the test. @@ -3596,8 +3586,6 @@ where needs_persist_flag: AtomicBool::new(false), 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()), @@ -3610,8 +3598,6 @@ where #[cfg(feature = "dnssec")] hrn_resolver: OMNameResolver::new(current_timestamp, params.best_block.height), - #[cfg(feature = "dnssec")] - pending_dns_onion_messages: Mutex::new(Vec::new()), #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), @@ -4948,27 +4934,12 @@ where } }; - 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::BlindedPathCreationFailed); + if self.flow.enqueue_async_payment_messages(invoice, payment_id, self.get_peers_for_blinded_path()).is_err() { + self.abandon_payment_with_reason(payment_id, PaymentFailureReason::BlindedPathCreationFailed); res = Err(Bolt12PaymentError::BlindedPathCreationFailed); return NotifyOption::DoPersist - } }; - 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 - ); - NotifyOption::DoPersist }); @@ -10216,24 +10187,7 @@ macro_rules! create_offer_builder { ($self: ident, $builder: ty) => { pub fn create_offer_builder( &$self, absolute_expiry: Option ) -> Result<$builder, Bolt12SemanticError> { - let node_id = $self.get_our_node_id(); - let expanded_key = &$self.inbound_payment_key; - let entropy = &*$self.entropy_source; - let secp_ctx = &$self.secp_ctx; - - let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::InvoiceRequest { nonce }; - let path = $self.create_blinded_paths_using_absolute_expiry(context, absolute_expiry) - .and_then(|paths| paths.into_iter().next().ok_or(())) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - let builder = OfferBuilder::deriving_signing_pubkey(node_id, expanded_key, nonce, secp_ctx) - .chain_hash($self.chain_hash) - .path(path); - - let builder = match absolute_expiry { - None => builder, - Some(absolute_expiry) => builder.absolute_expiry(absolute_expiry), - }; + let builder = $self.flow.create_offer_builder(absolute_expiry, None, $self.get_peers_for_blinded_path())?; Ok(builder.into()) } @@ -10289,23 +10243,7 @@ macro_rules! create_refund_builder { ($self: ident, $builder: ty) => { &$self, amount_msats: u64, absolute_expiry: Duration, payment_id: PaymentId, retry_strategy: Retry, route_params_config: RouteParametersConfig ) -> Result<$builder, Bolt12SemanticError> { - let node_id = $self.get_our_node_id(); - let expanded_key = &$self.inbound_payment_key; - let entropy = &*$self.entropy_source; - let secp_ctx = &$self.secp_ctx; - - let nonce = Nonce::from_entropy_source(entropy); - let context = OffersContext::OutboundPayment { payment_id, nonce, hmac: None }; - let path = $self.create_blinded_paths_using_absolute_expiry(context, Some(absolute_expiry)) - .and_then(|paths| paths.into_iter().next().ok_or(())) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; - - let 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) - .path(path); + let builder = $self.flow.create_refund_builder(amount_msats, absolute_expiry, payment_id, $self.get_peers_for_blinded_path())?; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop($self); @@ -10365,16 +10303,10 @@ where return Err(Bolt12SemanticError::MissingPaths) } - let node_id = self.get_our_node_id(); - let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - let nonce = Nonce::from_entropy_source(entropy); - let mut builder = OfferBuilder::deriving_signing_pubkey( - node_id, expanded_key, nonce, secp_ctx - ).chain_hash(self.chain_hash); + let mut builder = self.flow.create_offer_builder(None, Some(nonce), Vec::new())?; for path in message_paths_to_always_online_node { builder = builder.path(path); } @@ -10387,49 +10319,9 @@ where /// invoice's expiry will default to [`STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY`]. #[cfg(async_payments)] pub fn create_static_invoice_builder<'a>( - &self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option + &'a self, offer: &'a Offer, offer_nonce: Nonce, relative_expiry: Option ) -> Result, Bolt12SemanticError> { - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.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 relative_expiry = relative_expiry.unwrap_or(STATIC_INVOICE_DEFAULT_RELATIVE_EXPIRY); - let relative_expiry_secs: u32 = relative_expiry.as_secs().try_into().unwrap_or(u32::MAX); - - let created_at = self.duration_since_epoch(); - let payment_secret = inbound_payment::create_for_spontaneous_payment( - &self.inbound_payment_key, amount_msat, relative_expiry_secs, created_at.as_secs(), None - ).map_err(|()| Bolt12SemanticError::InvalidAmount)?; - - let payment_paths = self.create_blinded_payment_paths( - amount_msat, payment_secret, payment_context, relative_expiry_secs - ).map_err(|()| Bolt12SemanticError::MissingPaths)?; - - let nonce = Nonce::from_entropy_source(entropy); - let hmac = signer::hmac_for_held_htlc_available_context(nonce, expanded_key); - 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 { nonce, hmac, path_absolute_expiry } - ); - let async_receive_message_paths = self.create_blinded_paths(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)) + self.flow.create_static_invoice_builder(offer, offer_nonce, relative_expiry, self.list_usable_channels(), self.get_peers_for_blinded_path()) } /// Pays for an [`Offer`] using the given parameters by creating an [`InvoiceRequest`] and @@ -10511,74 +10403,17 @@ where payer_note: Option, payment_id: PaymentId, human_readable_name: Option, create_pending_payment: CPP, ) -> Result<(), Bolt12SemanticError> { - let expanded_key = &self.inbound_payment_key; let entropy = &*self.entropy_source; - let secp_ctx = &self.secp_ctx; - let nonce = Nonce::from_entropy_source(entropy); - let builder: InvoiceRequestBuilder = offer - .request_invoice(expanded_key, nonce, secp_ctx, payment_id)? - .into(); - let builder = builder.chain_hash(self.chain_hash)?; - - let builder = match quantity { - None => builder, - Some(quantity) => builder.quantity(quantity)?, - }; - let builder = match amount_msats { - None => builder, - Some(amount_msats) => builder.amount_msats(amount_msats)?, - }; - let builder = match payer_note { - None => builder, - Some(payer_note) => builder.payer_note(payer_note), - }; - let builder = match human_readable_name { - None => builder, - Some(hrn) => builder.sourced_from_human_readable_name(hrn), - }; - let invoice_request = builder.build_and_sign()?; - let hmac = payment_id.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers( - OffersContext::OutboundPayment { payment_id, nonce, hmac: Some(hmac) } - ); - let reply_paths = self.create_blinded_paths(context) - .map_err(|_| Bolt12SemanticError::MissingPaths)?; + let builder = self.flow.create_invoice_request_builder(offer, nonce, quantity, amount_msats, payer_note, human_readable_name, payment_id)?; + let invoice_request = builder.build_and_sign()?; let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self); - create_pending_payment(&invoice_request, nonce)?; - - self.enqueue_invoice_request(invoice_request, reply_paths) - } - - fn enqueue_invoice_request( - &self, - invoice_request: InvoiceRequest, - reply_paths: Vec, - ) -> Result<(), Bolt12SemanticError> { - 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); - } + self.flow.enqueue_invoice_request(invoice_request.clone(), payment_id, Some(nonce), self.get_peers_for_blinded_path())?; - Ok(()) + create_pending_payment(&invoice_request, nonce) } /// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion @@ -10606,8 +10441,6 @@ where pub fn request_refund_payment( &self, refund: &Refund ) -> Result { - let expanded_key = &self.inbound_payment_key; - let entropy = &*self.entropy_source; let secp_ctx = &self.secp_ctx; let amount_msats = refund.amount_msats(); @@ -10621,51 +10454,12 @@ where match self.create_inbound_payment(Some(amount_msats), relative_expiry, None) { Ok((payment_hash, payment_secret)) => { - let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {}); - let payment_paths = self.create_blinded_payment_paths( - 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 - )?; - let builder: InvoiceBuilder = builder.into(); + + let builder = self.flow.create_invoice_builder_from_refund(refund, payment_hash, payment_secret, self.list_usable_channels())?; + let invoice = builder.allow_mpp().build_and_sign(secp_ctx)?; - let nonce = Nonce::from_entropy_source(entropy); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); - 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)?; - - 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.clone()); - enqueue_onion_message_with_reply_paths( - message, refund.paths(), reply_paths, &mut pending_offers_messages - ); - } + self.flow.enqueue_invoice(invoice.clone(), refund, payment_hash, self.get_peers_for_blinded_path())?; Ok(invoice) }, @@ -10721,23 +10515,11 @@ where ) -> Result<(), ()> { let (onion_message, context) = self.hrn_resolver.resolve_name(payment_id, name, &*self.entropy_source)?; - let reply_paths = self.create_blinded_paths(MessageContext::DNSResolver(context))?; - let expiration = StaleExpiration::TimerTicks(1); - self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, route_params_config, amount_msats)?; - 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(onion_message.clone()), - MessageSendInstructions::WithSpecifiedReplyPath { - destination: destination.clone(), - reply_path: reply_path.clone(), - }, - )); - } - Ok(()) + + let expiration = StaleExpiration::TimerTicks(1); + self.pending_outbound_payments.add_new_awaiting_offer(payment_id, expiration, retry_strategy, route_params_config, amount_msats)?; + + self.flow.enqueue_dns_onion_message(onion_message, context, dns_resolvers, self.get_peers_for_blinded_path()).map_err(|_| ()) } /// Gets a payment secret and payment hash for use in an invoice given to a third party wishing @@ -10838,25 +10620,6 @@ where inbound_payment::get_payment_preimage(payment_hash, payment_secret, &self.inbound_payment_key) } - /// Creates a collection of blinded paths by delegating to [`MessageRouter`] based on - /// the path's intended lifetime. - /// - /// Whether or not the path is compact depends on whether the path is short-lived or long-lived, - /// respectively, based on the given `absolute_expiry` as seconds since the Unix epoch. See - /// [`MAX_SHORT_LIVED_RELATIVE_EXPIRY`]. - fn create_blinded_paths_using_absolute_expiry( - &self, context: OffersContext, absolute_expiry: Option, - ) -> Result, ()> { - let now = self.duration_since_epoch(); - let max_short_lived_absolute_expiry = now.saturating_add(MAX_SHORT_LIVED_RELATIVE_EXPIRY); - - if absolute_expiry.unwrap_or(Duration::MAX) <= max_short_lived_absolute_expiry { - self.create_compact_blinded_paths(context) - } else { - self.create_blinded_paths(MessageContext::Offers(context)) - } - } - pub(super) fn duration_since_epoch(&self) -> Duration { #[cfg(not(feature = "std"))] let now = Duration::from_secs( @@ -10887,42 +10650,10 @@ where .collect::>() } - /// Creates a collection of blinded paths by delegating to - /// [`MessageRouter::create_blinded_paths`]. - /// - /// Errors if the `MessageRouter` errors. - fn create_blinded_paths(&self, context: MessageContext) -> Result, ()> { - let recipient = self.get_our_node_id(); - let secp_ctx = &self.secp_ctx; - - let peers = self.get_peers_for_blinded_path() - .into_iter() - .map(|node| node.node_id) - .collect(); - - self.message_router - .create_blinded_paths(recipient, context, peers, secp_ctx) - .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) - } - - /// Creates a collection of blinded paths by delegating to - /// [`MessageRouter::create_compact_blinded_paths`]. - /// - /// Errors if the `MessageRouter` errors. - fn create_compact_blinded_paths(&self, context: OffersContext) -> Result, ()> { - let recipient = self.get_our_node_id(); - let secp_ctx = &self.secp_ctx; - - let peers = self.get_peers_for_blinded_path(); - - self.message_router - .create_compact_blinded_paths(recipient, MessageContext::Offers(context), peers, secp_ctx) - .and_then(|paths| (!paths.is_empty()).then(|| paths).ok_or(())) - } - + #[cfg(all(test, async_payments))] /// Creates multi-hop blinded payment paths for the given `amount_msats` by delegating to /// [`Router::create_blinded_payment_paths`]. - fn create_blinded_payment_paths( + pub(super) fn test_create_blinded_payment_paths( &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, relative_expiry_seconds: u32 ) -> Result, ()> { @@ -10956,16 +10687,6 @@ where ) } - #[cfg(all(test, async_payments))] - pub(super) fn test_create_blinded_payment_paths( - &self, amount_msats: Option, payment_secret: PaymentSecret, payment_context: PaymentContext, - relative_expiry_seconds: u32 - ) -> Result, ()> { - self.create_blinded_payment_paths( - amount_msats, payment_secret, payment_context, relative_expiry_seconds - ) - } - /// Gets a fake short channel id for use in receiving [phantom node payments]. These fake scids /// are used when constructing the phantom invoice's route hints. /// @@ -12414,29 +12135,13 @@ where .release_invoice_requests_awaiting_invoice() { let RetryableInvoiceRequest { invoice_request, nonce, .. } = retryable_invoice_request; - let hmac = payment_id.hmac_for_offer_payment(nonce, &self.inbound_payment_key); - 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(_) => {} - Err(_) => { - log_warn!(self.logger, - "Retry failed for an invoice request with payment_id: {}", - payment_id - ); - } - }, - Err(_) => { - log_warn!(self.logger, - "Retry failed for an invoice request with payment_id: {}. \ - Reason: router could not find a blinded path to include as the reply path", - payment_id - ); - } + + if self.flow.enqueue_invoice_request(invoice_request, payment_id, Some(nonce), self.get_peers_for_blinded_path()).is_err() { + log_warn!( + self.logger, + "Retry failed for invoice request with payment_id {}", + payment_id + ); } } } @@ -12458,7 +12163,6 @@ where fn handle_message( &self, message: OffersMessage, context: Option, responder: Option, ) -> Option<(OffersMessage, ResponseInstruction)> { - let secp_ctx = &self.secp_ctx; let expanded_key = &self.inbound_payment_key; macro_rules! handle_pay_invoice_res { @@ -12503,23 +12207,9 @@ where None => return None, }; - let nonce = match context { - None if invoice_request.metadata().is_some() => None, - Some(OffersContext::InvoiceRequest { nonce }) => Some(nonce), - _ => return None, - }; - - let invoice_request = match nonce { - Some(nonce) => match invoice_request.verify_using_recipient_data( - nonce, expanded_key, secp_ctx, - ) { - Ok(invoice_request) => invoice_request, - Err(()) => return None, - }, - None => match invoice_request.verify_using_metadata(expanded_key, secp_ctx) { - Ok(invoice_request) => invoice_request, - Err(()) => return None, - }, + let invoice_request = match self.flow.verify_invoice_request(invoice_request, context) { + Ok(invoice_request) => invoice_request, + Err(_) => return None, }; let amount_msats = match InvoiceBuilder::::amount_msats( @@ -12540,72 +12230,18 @@ where }, }; - let payment_context = PaymentContext::Bolt12Offer(Bolt12OfferContext { - offer_id: invoice_request.offer_id, - invoice_request: invoice_request.fields(), - }); - let payment_paths = match self.create_blinded_payment_paths( - Some(amount_msats), payment_secret, payment_context, relative_expiry - ) { - Ok(payment_paths) => payment_paths, - Err(()) => { - let error = Bolt12SemanticError::MissingPaths; - return Some((OffersMessage::InvoiceError(error.into()), responder.respond())); - }, - }; - - #[cfg(not(feature = "std"))] - let created_at = Duration::from_secs( - self.highest_seen_timestamp.load(Ordering::Acquire) as u64 + let (response, context) = self.flow.create_response_for_invoice_request( + &self.node_signer, invoice_request, amount_msats, + payment_hash, payment_secret, self.list_usable_channels() ); - 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::::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::::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| - self.node_signer.sign_bolt12_invoice(invoice) - ) - .map_err(InvoiceError::from) - }) - }; - - match response { - Ok(invoice) => { - let nonce = Nonce::from_entropy_source(&*self.entropy_source); - let hmac = payment_hash.hmac_for_offer_payment(nonce, expanded_key); - let context = MessageContext::Offers(OffersContext::InboundPayment { payment_hash, nonce, hmac }); - Some((OffersMessage::Invoice(invoice), responder.respond_with_reply_path(context))) - }, - Err(error) => Some((OffersMessage::InvoiceError(error.into()), responder.respond())), + match context { + Some(context) => Some((response, responder.respond_with_reply_path(context))), + None => Some((response, responder.respond())) } }, OffersMessage::Invoice(invoice) => { - let payment_id = match self.verify_bolt12_invoice(&invoice, context.as_ref()) { + let payment_id = match self.flow.verify_bolt12_invoice(&invoice, context.as_ref()) { Ok(payment_id) => payment_id, Err(()) => return None, }; @@ -12670,7 +12306,7 @@ where } fn release_pending_messages(&self) -> Vec<(OffersMessage, MessageSendInstructions)> { - core::mem::take(&mut self.pending_offers_messages.lock().unwrap()) + self.flow.get_and_clear_pending_offers_messages() } } @@ -12692,15 +12328,7 @@ where _responder: Option ) -> Option<(ReleaseHeldHtlc, ResponseInstruction)> { #[cfg(async_payments)] { - match _context { - AsyncPaymentsContext::InboundPayment { nonce, hmac, path_absolute_expiry } => { - if let Err(()) = signer::verify_held_htlc_available_context( - nonce, hmac, &self.inbound_payment_key - ) { return None } - if self.duration_since_epoch() > path_absolute_expiry { return None } - }, - _ => return None - } + if self.flow.verify_async_context(_context).is_err() { return None } return _responder.map(|responder| (ReleaseHeldHtlc {}, responder.respond())) } #[cfg(not(async_payments))] @@ -12709,13 +12337,10 @@ where fn handle_release_held_htlc(&self, _message: ReleaseHeldHtlc, _context: AsyncPaymentsContext) { #[cfg(async_payments)] { - let (payment_id, nonce, hmac) = match _context { - AsyncPaymentsContext::OutboundPayment { payment_id, hmac, nonce } => { - (payment_id, nonce, hmac) - }, + let payment_id = match self.flow.verify_async_context(_context) { + Ok(Some(payment_id)) => payment_id, _ => return }; - 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 @@ -12725,7 +12350,7 @@ where } fn release_pending_messages(&self) -> Vec<(AsyncPaymentsMessage, MessageSendInstructions)> { - core::mem::take(&mut self.pending_async_payments_messages.lock().unwrap()) + self.flow.get_and_clear_pending_async_messages() } } @@ -12789,7 +12414,7 @@ where } fn release_pending_messages(&self) -> Vec<(DNSResolverMessage, MessageSendInstructions)> { - core::mem::take(&mut self.pending_dns_onion_messages.lock().unwrap()) + self.flow.get_and_clear_pending_dns_messages() } } @@ -14760,8 +14385,6 @@ 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()), @@ -14776,8 +14399,6 @@ where #[cfg(feature = "dnssec")] hrn_resolver: OMNameResolver::new(highest_seen_timestamp, best_block_height), - #[cfg(feature = "dnssec")] - pending_dns_onion_messages: Mutex::new(Vec::new()), #[cfg(feature = "_test_utils")] testing_dnssec_proof_offer_resolution_override: Mutex::new(new_hash_map()), diff --git a/lightning/src/ln/offers_tests.rs b/lightning/src/ln/offers_tests.rs index 78cd40e9e34..9fa8e5f9dff 100644 --- a/lightning/src/ln/offers_tests.rs +++ b/lightning/src/ln/offers_tests.rs @@ -1400,7 +1400,7 @@ fn fails_authentication_when_handling_invoice_request() { expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); connect_peers(david, alice); - match &mut david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut david.node.flow.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::Node(alice_id), _ => panic!(), @@ -1425,7 +1425,7 @@ fn fails_authentication_when_handling_invoice_request() { .unwrap(); expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id); - match &mut david.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut david.node.flow.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::BlindedPath(invalid_path), _ => panic!(), @@ -1505,7 +1505,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { // Don't send the invoice request, but grab its reply path to use with a different request. let invalid_reply_path = { - let mut pending_offers_messages = david.node.pending_offers_messages.lock().unwrap(); + let mut pending_offers_messages = david.node.flow.pending_offers_messages.lock().unwrap(); let pending_invoice_request = pending_offers_messages.pop().unwrap(); pending_offers_messages.clear(); match pending_invoice_request.1 { @@ -1522,7 +1522,7 @@ fn fails_authentication_when_handling_invoice_for_offer() { // Swap out the reply path to force authentication to fail when handling the invoice since it // will be sent over the wrong blinded path. { - let mut pending_offers_messages = david.node.pending_offers_messages.lock().unwrap(); + let mut pending_offers_messages = david.node.flow.pending_offers_messages.lock().unwrap(); let mut pending_invoice_request = pending_offers_messages.first_mut().unwrap(); match &mut pending_invoice_request.1 { MessageSendInstructions::WithSpecifiedReplyPath { reply_path, .. } => @@ -1609,7 +1609,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); connect_peers(david, alice); - match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut alice.node.flow.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::Node(david_id), _ => panic!(), @@ -1640,7 +1640,7 @@ fn fails_authentication_when_handling_invoice_for_refund() { let expected_invoice = alice.node.request_refund_payment(&refund).unwrap(); - match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { + match &mut alice.node.flow.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 { MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } => *destination = Destination::BlindedPath(invalid_path), _ => panic!(), @@ -2231,7 +2231,7 @@ fn fails_paying_invoice_with_unknown_required_features() { destination: Destination::BlindedPath(reply_path), }; let message = OffersMessage::Invoice(invoice); - alice.node.pending_offers_messages.lock().unwrap().push((message, instructions)); + alice.node.flow.pending_offers_messages.lock().unwrap().push((message, instructions)); let onion_message = alice.onion_messenger.next_onion_message_for_peer(charlie_id).unwrap(); charlie.onion_messenger.handle_onion_message(alice_id, &onion_message);