Skip to content

Commit

Permalink
Move request_refund_payment to OffersMessageFlow
Browse files Browse the repository at this point in the history
  • Loading branch information
shaavan committed Nov 19, 2024
1 parent a59b356 commit e038951
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 167 deletions.
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);

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

0 comments on commit e038951

Please sign in to comment.