Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce OffersMessageFlow #3412

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
157 changes: 5 additions & 152 deletions lightning/src/ln/channelmanager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::events::FundingInfo;
use crate::blinded_path::message::{AsyncPaymentsContext, MessageContext, OffersContext};
use crate::blinded_path::NodeIdLookUp;
use crate::blinded_path::message::{BlindedMessagePath, MessageForwardNode};
use crate::blinded_path::payment::{BlindedPaymentPath, Bolt12RefundContext, PaymentConstraints, PaymentContext, ReceiveTlvs};
use crate::blinded_path::payment::{BlindedPaymentPath, PaymentConstraints, PaymentContext, ReceiveTlvs};
use crate::chain;
use crate::chain::{Confirm, ChannelMonitorUpdateStatus, Watch, BestBlock};
use crate::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator, LowerBoundedFeeEstimator};
Expand Down Expand Up @@ -65,11 +65,10 @@ use crate::ln::msgs::{ChannelMessageHandler, DecodeError, LightningError};
use crate::ln::outbound_payment;
use crate::ln::outbound_payment::{OutboundPayments, PendingOutboundPayment, RetryableInvoiceRequest, SendAlongPathArgs, StaleExpiration};
use crate::ln::wire::Encode;
use crate::offers::invoice::{Bolt12Invoice, DEFAULT_RELATIVE_EXPIRY, DerivedSigningPubkey, InvoiceBuilder};
use crate::offers::invoice::Bolt12Invoice;
use crate::offers::invoice_request::{InvoiceRequest, InvoiceRequestBuilder};
use crate::offers::nonce::Nonce;
use crate::offers::parse::Bolt12SemanticError;
use crate::offers::refund::Refund;
use crate::offers::signer;
#[cfg(async_payments)]
use crate::offers::static_invoice::StaticInvoice;
Expand Down Expand Up @@ -1993,54 +1992,6 @@ where
/// # }
/// ```
///
/// ## BOLT 12 Refunds
///
/// Use [`request_refund_payment`] to send a [`Bolt12Invoice`] for receiving the refund. Similar to
/// *creating* an [`Offer`], this is stateless as it represents an inbound payment.
///
/// ```
/// # use lightning::events::{Event, EventsProvider, PaymentPurpose};
/// # use lightning::ln::channelmanager::{AChannelManager, OffersMessageCommons};
/// # use lightning::offers::refund::Refund;
/// #
/// # fn example<T: AChannelManager>(channel_manager: T, refund: &Refund) {
/// # let channel_manager = channel_manager.get_cm();
/// let known_payment_hash = match channel_manager.request_refund_payment(refund) {
/// Ok(invoice) => {
/// let payment_hash = invoice.payment_hash();
/// println!("Requesting refund payment {}", payment_hash);
/// payment_hash
/// },
/// Err(e) => panic!("Unable to request payment for refund: {:?}", e),
/// };
///
/// // On the event processing thread
/// channel_manager.process_pending_events(&|event| {
/// match event {
/// Event::PaymentClaimable { payment_hash, purpose, .. } => match purpose {
/// PaymentPurpose::Bolt12RefundPayment { payment_preimage: Some(payment_preimage), .. } => {
/// assert_eq!(payment_hash, known_payment_hash);
/// println!("Claiming payment {}", payment_hash);
/// channel_manager.claim_funds(payment_preimage);
/// },
/// PaymentPurpose::Bolt12RefundPayment { payment_preimage: None, .. } => {
/// println!("Unknown payment hash: {}", payment_hash);
/// },
/// // ...
/// # _ => {},
/// },
/// Event::PaymentClaimed { payment_hash, amount_msat, .. } => {
/// assert_eq!(payment_hash, known_payment_hash);
/// println!("Claimed {} msats", amount_msat);
/// },
/// // ...
/// # _ => {},
/// }
/// Ok(())
/// });
/// # }
/// ```
///
/// # Persistence
///
/// Implements [`Writeable`] to write out all channel state to disk. Implies [`peer_disconnected`] for
Expand Down Expand Up @@ -2633,6 +2584,7 @@ const MAX_NO_CHANNEL_PEERS: usize = 250;
/// become invalid over time as channels are closed. Thus, they are only suitable for short-term use.
///
/// [`Offer`]: crate::offers::offer
/// [`Refund`]: crate::offers::refund
pub const MAX_SHORT_LIVED_RELATIVE_EXPIRY: Duration = Duration::from_secs(60 * 60 * 24);

/// Used by [`ChannelManager::list_recent_payments`] to express the status of recent payments.
Expand Down Expand Up @@ -9573,7 +9525,7 @@ where
/// 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<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, MR: Deref, L: Deref> ChannelManager<M, T, ES, NS, SP, F, R, MR, L>
where
Expand All @@ -9587,106 +9539,6 @@ where
MR::Target: MessageRouter,
L::Target: Logger,
{
/// Creates a [`Bolt12Invoice`] for a [`Refund`] and enqueues it to be sent via an onion
/// message.
///
/// The resulting invoice uses a [`PaymentHash`] recognized by the [`ChannelManager`] and a
/// [`BlindedPaymentPath`] containing the [`PaymentSecret`] needed to reconstruct the
/// corresponding [`PaymentPreimage`]. It is returned purely for informational purposes.
///
/// # Limitations
///
/// Requires a direct connection to an introduction node in [`Refund::paths`] or to
/// [`Refund::payer_signing_pubkey`], if empty. This request is best effort; an invoice will be
/// sent to each node meeting the aforementioned criteria, but there's no guarantee that they
/// will be received and no retries will be made.
///
/// # Errors
///
/// Errors if:
/// - the refund is for an unsupported chain, or
/// - the parameterized [`Router`] is unable to create a blinded payment path or reply path for
/// the invoice.
///
/// [`Bolt12Invoice`]: crate::offers::invoice::Bolt12Invoice
pub fn request_refund_payment(
&self, refund: &Refund
) -> Result<Bolt12Invoice, Bolt12SemanticError> {
let expanded_key = &self.inbound_payment_key;
let entropy = &*self.entropy_source;
let secp_ctx = &self.secp_ctx;

let amount_msats = refund.amount_msats();
let relative_expiry = DEFAULT_RELATIVE_EXPIRY.as_secs() as u32;

if refund.chain() != self.chain_hash {
return Err(Bolt12SemanticError::UnsupportedChain);
}

let _persistence_guard = PersistenceNotifierGuard::notify_on_drop(self);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@TheBlueMatt Do you know why we need the PersistenceNotifierGuard here? This code sends an invoice for a refund (i.e., an inbound payment). IIUC, we don't persist pending_offers_messages, so it seems we don't need it.


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(
amount_msats, payment_secret, payment_context
)
.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<DerivedSigningPubkey> = builder.into();
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 {
reply_paths
.iter()
.flat_map(|reply_path| refund.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(),
};
let message = OffersMessage::Invoice(invoice.clone());
pending_offers_messages.push((message, instructions));
});
}

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

/// Pays for an [`Offer`] looked up using [BIP 353] Human Readable Names resolved by the DNS
/// resolver(s) at `dns_resolvers` which resolve names according to bLIP 32.
///
Expand Down Expand Up @@ -12133,6 +11985,7 @@ where
/// [`Refund`]s, and any reply paths.
///
/// [`Offer`]: crate::offers::offer
/// [`Refund`]: crate::offers::refund
pub message_router: MR,
/// The Logger for use in the ChannelManager and which may be used to log information during
/// deserialization.
Expand Down
26 changes: 13 additions & 13 deletions lightning/src/ln/offers_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ fn creates_and_pays_for_refund_using_two_hop_blinded_path() {
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);

let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap();

connect_peers(alice, charlie);

Expand Down Expand Up @@ -784,7 +784,7 @@ fn creates_and_pays_for_refund_using_one_hop_blinded_path() {
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);

let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap();

let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
Expand Down Expand Up @@ -889,7 +889,7 @@ fn pays_for_refund_without_blinded_paths() {
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);

let payment_context = PaymentContext::Bolt12Refund(Bolt12RefundContext {});
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap();

let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();
bob.onion_messenger.handle_onion_message(alice_id, &onion_message);
Expand Down Expand Up @@ -1044,7 +1044,7 @@ fn send_invoice_for_refund_with_distinct_reply_path() {
}
expect_recent_payment!(alice, RecentPaymentDetails::AwaitingInvoice, payment_id);

let _expected_invoice = david.node.request_refund_payment(&refund).unwrap();
let _expected_invoice = david.offers_handler.request_refund_payment(&refund).unwrap();

connect_peers(david, bob);

Expand Down Expand Up @@ -1324,7 +1324,7 @@ fn creates_refund_with_blinded_path_using_unannounced_introduction_node() {
}
expect_recent_payment!(bob, RecentPaymentDetails::AwaitingInvoice, payment_id);

let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap();

let onion_message = alice.onion_messenger.next_onion_message_for_peer(bob_id).unwrap();

Expand Down Expand Up @@ -1608,7 +1608,7 @@ fn fails_authentication_when_handling_invoice_for_refund() {
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);

// Send the invoice directly to David instead of using a blinded path.
let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap();

connect_peers(david, alice);
match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 {
Expand Down Expand Up @@ -1640,7 +1640,7 @@ fn fails_authentication_when_handling_invoice_for_refund() {
assert_eq!(path.introduction_node(), &IntroductionNode::NodeId(charlie_id));
}

let expected_invoice = alice.node.request_refund_payment(&refund).unwrap();
let expected_invoice = alice.offers_handler.request_refund_payment(&refund).unwrap();

match &mut alice.node.pending_offers_messages.lock().unwrap().first_mut().unwrap().1 {
MessageSendInstructions::WithSpecifiedReplyPath { destination, .. } =>
Expand Down Expand Up @@ -1773,7 +1773,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() {
.unwrap()
.build().unwrap();

match alice.node.request_refund_payment(&refund) {
match alice.offers_handler.request_refund_payment(&refund) {
Ok(_) => panic!("Expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
}
Expand All @@ -1782,7 +1782,7 @@ fn fails_creating_refund_or_sending_invoice_without_connected_peers() {
args.send_channel_ready = (true, true);
reconnect_nodes(args);

assert!(alice.node.request_refund_payment(&refund).is_ok());
assert!(alice.offers_handler.request_refund_payment(&refund).is_ok());
}

/// Fails creating an invoice request when the offer contains an unsupported chain.
Expand Down Expand Up @@ -1832,7 +1832,7 @@ fn fails_sending_invoice_with_unsupported_chain_for_refund() {
.chain(Network::Signet)
.build().unwrap();

match alice.node.request_refund_payment(&refund) {
match alice.offers_handler.request_refund_payment(&refund) {
Ok(_) => panic!("Expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::UnsupportedChain),
}
Expand Down Expand Up @@ -2055,7 +2055,7 @@ fn fails_sending_invoice_without_blinded_payment_paths_for_refund() {
.unwrap()
.build().unwrap();

match alice.node.request_refund_payment(&refund) {
match alice.offers_handler.request_refund_payment(&refund) {
Ok(_) => panic!("Expected error"),
Err(e) => assert_eq!(e, Bolt12SemanticError::MissingPaths),
}
Expand Down Expand Up @@ -2106,7 +2106,7 @@ fn fails_paying_invoice_more_than_once() {
expect_recent_payment!(david, RecentPaymentDetails::AwaitingInvoice, payment_id);

// Alice sends the first invoice
alice.node.request_refund_payment(&refund).unwrap();
alice.offers_handler.request_refund_payment(&refund).unwrap();

connect_peers(alice, charlie);

Expand All @@ -2126,7 +2126,7 @@ fn fails_paying_invoice_more_than_once() {
disconnect_peers(alice, &[charlie]);

// Alice sends the second invoice
alice.node.request_refund_payment(&refund).unwrap();
alice.offers_handler.request_refund_payment(&refund).unwrap();

connect_peers(alice, charlie);
connect_peers(david, bob);
Expand Down
Loading
Loading