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

Support receiving async payments #3440

Conversation

valentinewallace
Copy link
Contributor

@valentinewallace valentinewallace commented Dec 4, 2024

Finishes support for receiving async payments.

The last piece after this for the async-receive side is support for being the async receiver's LSP and serving invoices on their behalf.

Partially addresses #2298

Based on #3408, #3517
Also based on #2933, it enables the use of one of the test utils due to more uniform handling of HTLC receive failures.

@valentinewallace
Copy link
Contributor Author

This still needs some work and tests but I opened it in case it's useful for reviewing #3408.

@valentinewallace valentinewallace mentioned this pull request Dec 4, 2024
26 tasks
@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch 2 times, most recently from 904adf6 to 6f7fcba Compare January 9, 2025 20:50
@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch from 6f7fcba to f2ac259 Compare January 17, 2025 20:12
@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch 2 times, most recently from 9262e08 to f6370df Compare January 27, 2025 19:19
@valentinewallace valentinewallace marked this pull request as ready for review January 27, 2025 19:39
use bitcoin::secp256k1;
use bitcoin::secp256k1::Secp256k1;

use core::convert::Infallible;
use core::time::Duration;

// Goes through the async receive onion message flow, returning the final release_held_htlc OM.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

For reviewers: the last commit here is rather large here but it's all tests and test util changes. Let me know if it would be helpful to break it up!

My text editor removes trailing whitespace automatically so let's make the
removal official.
Store an absolute expiry in blinded message contexts for inbound async
payments. Without this, anyone with the path corresponding to this context is
able to trivially ask if we're online forever.
If we receive a message that an HTLC is being held upstream for us, send a
reply onion message back releasing it since we are online to receive the
corresponding payment.
As part of receiving an async payment, we need to verify the sender's original
invoice request. Therefore, add support for parsing the invreq contained in the
onion and storing it in PendingHTLCForwards to prepare for when we add this
verification in an upcoming commit. The invreq also needs to be bubbled up for
inclusion in the PaymentClaimable event's PaymentPurpose.
@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch from f6370df to c21fea2 Compare January 30, 2025 18:49
@valentinewallace valentinewallace added the weekly goal Someone wants to land this this week label Feb 3, 2025
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

Looks good. Need to review the tests still.

Copy link
Collaborator

@TheBlueMatt TheBlueMatt left a comment

Choose a reason for hiding this comment

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

I only skimmed the tests but they LGTM

@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch from c21fea2 to 4b929c3 Compare February 6, 2025 17:54
@valentinewallace
Copy link
Contributor Author

Added a comment:

diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs
index 46304b1ae..0b7673158 100644
--- a/lightning/src/events/mod.rs
+++ b/lightning/src/events/mod.rs
@@ -1867,6 +1867,9 @@ impl MaybeReadable for Event {
                                                (11, payment_context, option),
                                                (13, payment_id, option),
                                        });
+                                       // We can pass in `None` for the invoice request parameter here because if this is an
+                                       // offer payment we will only ever have written a `Bolt12OfferContext` (which already
+                                       // contains the invreq), never an `AsyncBolt12OfferContext`.
                                        let purpose = match payment_secret {
                                                Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context, None)
                                                        .map_err(|()| msgs::DecodeError::InvalidValue)?,

Comment on lines -2777 to +2796
assert_eq!(events_2.len(), 2);
// If we successfully decode the HTLC onion but then fail later in
// process_pending_htlc_forwards, then we'll queue the failure and generate a new
// `ProcessPendingHTLCForwards` event. If we fail during the process of decoding the HTLC,
// we'll fail it immediately with no intermediate forwarding event.
assert!(events_2.len() == 1 || events_2.len() == 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this pass when two events is expected but a regression results in only one? Is there another way to assert that would avoid this? Maybe through what's in expected_failure or in how it set?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IIRC I couldn't find a way to tell with the current code. This would be fixed if #1302 (comment) (or possibly #3561) were addressed, though. There really shouldn't be 1 event ever except for the bug documented in the former link.

After a lot of setup in prior commits, here we finally finish support for
receiving HTLCs paid to static BOLT 12 invoices. It amounts to verifying the
invoice request contained within the onion and generating the right
PaymentPurpose for the claimable event.
Previously, this test util did not account for config overrides supplied at
node creation time. Uncovered because it caused test nodes to be unable to
forward over private channels created with this util because that is not the
default.
@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch from 4b929c3 to c5bd26f Compare February 7, 2025 20:39
@valentinewallace
Copy link
Contributor Author

Refactored from_parts a bit:

diff --git a/lightning/src/events/mod.rs b/lightning/src/events/mod.rs
index 0b7673158..bbaaca4e8 100644
--- a/lightning/src/events/mod.rs
+++ b/lightning/src/events/mod.rs
@@ -28,7 +28,6 @@ use crate::ln::msgs;
 use crate::ln::types::ChannelId;
 use crate::types::payment::{PaymentPreimage, PaymentHash, PaymentSecret};
 use crate::offers::invoice::Bolt12Invoice;
-use crate::offers::invoice_request::VerifiedInvoiceRequest;
 use crate::onion_message::messenger::Responder;
 use crate::routing::gossip::NetworkUpdate;
 use crate::routing::router::{BlindedTail, Path, RouteHop, RouteParameters};
@@ -179,9 +178,10 @@ impl PaymentPurpose {
 		}
 	}
 
+	/// Errors when provided an `AsyncBolt12OfferContext`, see below.
 	pub(crate) fn from_parts(
 		payment_preimage: Option<PaymentPreimage>, payment_secret: PaymentSecret,
-		payment_context: Option<PaymentContext>, invreq: Option<VerifiedInvoiceRequest>,
+		payment_context: Option<PaymentContext>
 	) -> Result<Self, ()> {
 		match payment_context {
 			None => {
@@ -205,16 +205,10 @@ impl PaymentPurpose {
 				})
 			},
 			Some(PaymentContext::AsyncBolt12Offer(_)) => {
-				let invoice_request = invreq.ok_or(())?;
-				if payment_preimage.is_none() { return Err(()) }
-				Ok(PaymentPurpose::Bolt12OfferPayment {
-					payment_preimage,
-					payment_secret,
-					payment_context: Bolt12OfferContext {
-						offer_id: invoice_request.offer_id,
-						invoice_request: invoice_request.fields(),
-					},
-				})
+				// Callers are expected to convert from `AsyncBolt12OfferContext` to `Bolt12OfferContext`
+				// using the invoice request provided in the payment onion prior to calling this method.
+				debug_assert!(false);
+				Err(())
 			}
 		}
 	}
@@ -1867,11 +1861,8 @@ impl MaybeReadable for Event {
 						(11, payment_context, option),
 						(13, payment_id, option),
 					});
-					// We can pass in `None` for the invoice request parameter here because if this is an
-					// offer payment we will only ever have written a `Bolt12OfferContext` (which already
-					// contains the invreq), never an `AsyncBolt12OfferContext`.
 					let purpose = match payment_secret {
-						Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context, None)
+						Some(secret) => PaymentPurpose::from_parts(payment_preimage, secret, payment_context)
 							.map_err(|()| msgs::DecodeError::InvalidValue)?,
 						None if payment_preimage.is_some() => PaymentPurpose::SpontaneousPayment(payment_preimage.unwrap()),
 						None => return Err(msgs::DecodeError::InvalidValue),
diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index cc7f4ad4f..b4fb5312f 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -6265,7 +6265,6 @@ where
 											payment_preimage,
 											payment_data.payment_secret,
 											payment_context,
-											None,
 										) {
 											Ok(purpose) => purpose,
 											Err(()) => {
@@ -6303,9 +6302,13 @@ where
 													fail_htlc!(claimable_htlc, payment_hash);
 												}
 											};
+											let payment_purpose_context = PaymentContext::Bolt12Offer(Bolt12OfferContext {
+												offer_id: verified_invreq.offer_id,
+												invoice_request: verified_invreq.fields(),
+											});
 											match events::PaymentPurpose::from_parts(
-												Some(keysend_preimage), payment_data.payment_secret, payment_context,
-												Some(verified_invreq),
+												Some(keysend_preimage), payment_data.payment_secret,
+												Some(payment_purpose_context),
 											) {
 												Ok(purpose) => purpose,
 												Err(()) => {

jkczyz
jkczyz previously approved these changes Feb 7, 2025
Copy link
Contributor

@jkczyz jkczyz left a comment

Choose a reason for hiding this comment

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

LGTM

@TheBlueMatt
Copy link
Collaborator

Feel free to squash.

@valentinewallace valentinewallace force-pushed the 2024-12-async-payment-receive branch from 5144036 to 95e453a Compare February 7, 2025 23:29
jkczyz
jkczyz previously approved these changes Feb 7, 2025
In the previous commit we completed support for async receive from an
always-online sender to an often-offline receiver, minus support for acting as
the async receiver's always-online channel counterparty.
@valentinewallace
Copy link
Contributor Author

Squashed and had to push one more time to fix CI:

diff --git a/lightning/src/ln/channelmanager.rs b/lightning/src/ln/channelmanager.rs
index 9c4367a5b..a9d934056 100644
--- a/lightning/src/ln/channelmanager.rs
+++ b/lightning/src/ln/channelmanager.rs
@@ -4693,7 +4693,7 @@ where
                self.pending_outbound_payments.test_add_new_pending_payment(payment_hash, recipient_onion, payment_id, route, None, &self.entropy_source, best_block_height)
        }

-       #[cfg(test)]
+       #[cfg(all(test, async_payments))]
        pub(crate) fn test_modify_pending_payment<Fn>(
                &self, payment_id: &PaymentId, mut callback: Fn
        ) where Fn: FnMut(&mut PendingOutboundPayment) {

let htlc_updates = get_htlc_update_msgs!(nodes[0], nodes[1].node.get_our_node_id());
assert_eq!(htlc_updates.update_add_htlcs.len(), 1);
let mut events = nodes[0].node.get_and_clear_pending_msg_events();
assert_eq!(events.len(), 1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

For this kind of test it would be really nice to check that we didn't release the HTLC prior to handling the onion message above.

@TheBlueMatt TheBlueMatt merged commit f5c0433 into lightningdevkit:main Feb 8, 2025
23 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
weekly goal Someone wants to land this this week
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants