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 to multi-hop blinded paths #2688

Conversation

valentinewallace
Copy link
Contributor

@valentinewallace valentinewallace commented Oct 26, 2023

Title. Currently, we only support receiving to 1-hop blinded paths. Diff is largely error handling.

  • Add test coverage for malformed HTLC ser

Based on #2540.

@codecov-commenter
Copy link

codecov-commenter commented Oct 26, 2023

Codecov Report

Attention: 41 lines in your changes are missing coverage. Please review.

Comparison is base (ec8e0fe) 88.53% compared to head (6b66271) 89.31%.
Report is 35 commits behind head on main.

Files Patch % Lines
lightning/src/ln/channelmanager.rs 87.56% 13 Missing and 10 partials ⚠️
lightning/src/ln/channel.rs 90.00% 7 Missing and 4 partials ⚠️
lightning/src/ln/blinded_payment_tests.rs 98.31% 3 Missing and 1 partial ⚠️
lightning/src/ln/msgs.rs 90.00% 1 Missing and 1 partial ⚠️
lightning/src/ln/onion_payment.rs 97.77% 0 Missing and 1 partial ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2688      +/-   ##
==========================================
+ Coverage   88.53%   89.31%   +0.77%     
==========================================
  Files         115      115              
  Lines       91011    96503    +5492     
  Branches    91011    96503    +5492     
==========================================
+ Hits        80580    86194    +5614     
+ Misses       8004     7947      -57     
+ Partials     2427     2362      -65     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@valentinewallace valentinewallace force-pushed the 2023-10-multihop-blinded-recv branch 2 times, most recently from cfec513 to bf84b67 Compare November 9, 2023 22:30
@valentinewallace
Copy link
Contributor Author

Still ironing out some improvements to the code post-rebase of #2540, will get an update out soon.

@valentinewallace valentinewallace added this to the 0.0.119 milestone Dec 4, 2023
@valentinewallace valentinewallace marked this pull request as ready for review December 4, 2023 21:12
lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
@valentinewallace valentinewallace force-pushed the 2023-10-multihop-blinded-recv branch 2 times, most recently from c6d549e to 9b89b7e Compare December 6, 2023 20:59
@valentinewallace
Copy link
Contributor Author

Addressed feedback. Planning to add some test coverage for the new malformed HTLC ser changes, added a TODO.

Will be used to read encrypted_tlvs on non-intro-node onion receipt.
Support for receiving to multi-hop blinded payment paths will be completed in
the next commit, sans error handling.
The only remaining step is to use the update_add blinding point in decoding
inbound onion payloads.

Error handling will be completed in upcoming commits.
For use in supporting receiving to multi-hop blinded paths.
@valentinewallace
Copy link
Contributor Author

Rebased.

lightning/src/ln/onion_payment.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
Comment on lines 7477 to 7564
&HTLCUpdateAwaitingACK::FailMalformedHTLC {
htlc_id, failure_code, sha256_of_onion
} => {
// We don't want to break downgrading by adding a new variant, so write a dummy
// `::FailHTLC` variant and write the real malformed error as an optional TLV.
malformed_htlcs.push((htlc_id, failure_code, sha256_of_onion));

let dummy_err_packet = msgs::OnionErrorPacket { data: Vec::new() };
2u8.write(writer)?;
htlc_id.write(writer)?;
dummy_err_packet.write(writer)?;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

When downgrading, would these essentially be left in the holding cell permanently? Or are they dropped as a no-op when freeing the holding cell?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, downgraded nodes will use this dummy htlc data to fail this htlc backwards. This prevents them from dropping the malformed HTLCs (which are written as optional TLVs) on read post-downgrade, which would cause them to not fail backwards and eventually be FC'd on.

lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
Comment on lines 2526 to 2533
/// A wire message that fails an HTLC backwards sometime after the update_add_htlc's corresponding
/// RAA has been processed, as opposed to failing on initial update_add receipt. Useful for
/// [`Channel::fail_htlc`] to fail with either [`msgs::UpdateFailMalformedHTLC`] or
/// [`msgs::UpdateFailHTLC`] as needed.
trait PostRAAFailHTLCMessage {
type FailureReason: Clone;
fn new(htlc_id: u64, channel_id: ChannelId, reason: Self::FailureReason) -> Self;
fn message_name() -> &'static str;
fn inbound_htlc_state(reason: Self::FailureReason) -> InboundHTLCState;
fn htlc_update_awaiting_ack(htlc_id: u64, reason: Self::FailureReason) -> HTLCUpdateAwaitingACK;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Given that the err_packet parameter to fail_htlc is what is being generalized, it seems more natural to have those types implement the trait. That way most of the methods would be associated functions and have a &self parameter rather than having them all be named parameters (i.e., reason as phrased here).

Also, since you could parameterize other functions with the error type, it means you don't need to specify a type parameter at the call site. Later this avoid duplicating queue_fail_htlc as you'd be able to generalize its parameter instead.

Comment on lines 2874 to 2894
/// Used for failing back with [`msgs::UpdateFailMalformedHTLC`]. For now, this is used when we
/// want to fail blinded HTLCs where we are not the intro node.
///
/// See [`Self::queue_fail_htlc`] for more info.
pub fn queue_fail_malformed_htlc<L: Deref>(
&mut self, htlc_id_arg: u64, failure_code: u16, sha256_of_onion: [u8; 32], logger: &L
) -> Result<(), ChannelError> where L::Target: Logger {
self.fail_htlc::<L, msgs::UpdateFailMalformedHTLC>(
htlc_id_arg, (failure_code, sha256_of_onion), true, logger
).map(|msg_opt| assert!(msg_opt.is_none(), "We forced holding cell?"))
}

Copy link
Contributor

Choose a reason for hiding this comment

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

You shouldn't need to duplicate this. See earlier comment on the aded trait.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, although see #2688 (comment).

Copy link
Contributor Author

@valentinewallace valentinewallace Dec 12, 2023

Choose a reason for hiding this comment

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

Re-duplicated after offline discussion since it's more readable in ChannelManager this way and helps avoid increasing the visibility of some internal channel structs.

Comment on lines +4390 to +4427
HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
log_trace!(self.logger, "Failing malformed HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id);
if let Err(e) = chan.queue_fail_malformed_htlc(htlc_id, failure_code, sha256_of_onion, &self.logger) {
if let ChannelError::Ignore(msg) = e {
log_trace!(self.logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg);
} else {
panic!("Stated return value requirements in queue_fail_malformed_htlc() were not met");
}
// fail-backs are best-effort, we probably already have one
// pending, and if not that's OK, if not, the channel is on
// the chain and sending the HTLC-Timeout is their problem.
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be able to DRY this up, too?

Copy link
Contributor Author

@valentinewallace valentinewallace Dec 11, 2023

Choose a reason for hiding this comment

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

I don't think so, because queue_fail_htlc will have a statically chosen concrete underlying type of FailHTLCContents at compile time, IIUC.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, but the error type and everything downstream of it is identical.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Outdated Show resolved Hide resolved
}
fn message_name() -> &'static str { "update_fail_malformed_htlc" }
fn inbound_htlc_state(self) -> InboundHTLCState {
let (failure_code, sha256_of_onion) = self;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we normalize the order so this doesn't need to be destructured?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got rid of the destructuring. Not sure I got what you meant by "normalize."

Copy link
Contributor

Choose a reason for hiding this comment

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

By that I meant to keep the order of (failure_code, sha256_of_onion) the same between here and in InboundHTLCRemovalReason::FailMalformed. That way you just need to wrap self here rather than accessing each item in the tuple.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh, okay. I'll pass on this for now but I'll do it if there ends up being a follow-up to this PR.

lightning/src/ln/channelmanager.rs Outdated Show resolved Hide resolved
lightning/src/ln/channelmanager.rs Show resolved Hide resolved
@valentinewallace
Copy link
Contributor Author

Squashed and addressed feedback with 1 new fixup.

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.

LGTM, feel free to squash. A few nits but they're not blocking.

lightning/src/ln/msgs.rs Outdated Show resolved Hide resolved
lightning/src/ln/channel.rs Show resolved Hide resolved
Comment on lines +4390 to +4427
HTLCForwardInfo::FailMalformedHTLC { htlc_id, failure_code, sha256_of_onion } => {
log_trace!(self.logger, "Failing malformed HTLC back to channel with short id {} (backward HTLC ID {}) after delay", short_chan_id, htlc_id);
if let Err(e) = chan.queue_fail_malformed_htlc(htlc_id, failure_code, sha256_of_onion, &self.logger) {
if let ChannelError::Ignore(msg) = e {
log_trace!(self.logger, "Failed to fail HTLC with ID {} backwards to short_id {}: {}", htlc_id, short_chan_id, msg);
} else {
panic!("Stated return value requirements in queue_fail_malformed_htlc() were not met");
}
// fail-backs are best-effort, we probably already have one
// pending, and if not that's OK, if not, the channel is on
// the chain and sending the HTLC-Timeout is their problem.
continue;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Sure, but the error type and everything downstream of it is identical.

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

For context, blinded HTLCs where we are not the intro node must always be
failed back with malformed and invalid_onion_blinding error per BOLT 4.

Prior to supporting blinded payments, the only way for an update_malformed to
be returned from Channel was if an onion was actually found to be malformed
during initial update_add processing. This meant that any malformed HTLCs would
never live in the holding cell but instead would be returned directly upon
initial RAA processing.

Now, we need to be able to store these HTLCs in the holding cell because the
HTLC failure necessitating an update_malformed may come long after the RAA is
initially processed, and we may not be a state to send the update_malformed
message at that time.

Therefore, add a new holding cell HTLC variant for blinded non-intro node
HTLCs, which will signal to Channel to fail with malformed and the correct
error code.
Currently it returns only update_fail, but we'll want it to be able to return
update_malformed as well in upcoming commits. We'll use this for correctly
failing blinded received HTLCs backwards with malformed and
invalid_onion_blinding error per BOLT 4.
Useful for failing blinded payments back with malformed, and will also be
useful in the future when we move onion decoding into
process_pending_htlc_forwards, after which Channel::fail_htlc will be used for
all malformed htlcs.
Necessary to tell the Channel how to fail these htlcs.
Makes the next commit adding support for failing blinded HTLCs in said method
easier to read.
If an HTLC fails after its RAA is processed, it is failed back with
ChannelManager::fail_htlc_backwards_internal. This method will now correctly
inform the channel that this HTLC is blinded and to construct an
update_malformed message accordingly.
If a blinded recipient to a multihop blinded path needs to fail back a
malformed HTLC, they should use error code INVALID_ONION_BLINDING and a zeroed
out onion hash per BOLT 4.
And use it in the multihop blinded path receive failure test. Will be used in
the next commit to test receiving an invalid blinded final onion payload.

We can't use the existing get_route test util here because blinded payments
rely on the sender adding a random shadow CLTV offset to the final hop; without
this the payment will be failed with cltv-expiry-too-soon.
If a recipient behind a multihop blinded path fails to decode their onion
payload, they should fail backwards with error code INVALID_ONION_BLINDING and
a zeroed out onion hash per BOLT 4.
If a blinded HTLC does not satisfy the receiver's requirements, e.g. bad CLTV
or amount, they should malformed-fail backwards with error code
INVALID_ONION_BLINDING and a zeroed out onion hash per BOLt 4.
If a blinded HTLC errors when added to a Channel, such as if the recipient has
already sent a shutdown message, they should malformed-fail backwards with
error code INVALID_ONION_BLINDING and a zeroed out onion hash per BOLT 4.
.. contained within their encrypted payload.
Because we now support receiving to multi-hop blinded paths.
Although this new check is unreachable right now, it helps prevent potential
future errors where we incorrectly fail blinded HTLCs with an unblinded error.
@valentinewallace
Copy link
Contributor Author

Squashed the fixup and added another commit addressing #2688 (comment). Sorry if I'm being dense on DRYing the match arms, but happy to address that and #2688 (comment) in a follow-up.

@valentinewallace valentinewallace merged commit 9856fb6 into lightningdevkit:main Dec 13, 2023
14 of 15 checks passed
TheBlueMatt added a commit that referenced this pull request Jan 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants