Skip to content

Add LSPS5 DOS protections. #3993

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

martinsaposnic
Copy link
Contributor

@martinsaposnic martinsaposnic commented Aug 7, 2025

When handling an incoming LSPS5 request, the manager will check
if the counterparty is 'engaged' in some way before responding.
Engaged meaning = active channel | LSPS2 active operation | LSPS1 active operation.

Logic: If not engaged then reject request;

A single test is added only checking for the active channel condition,
because it's not super easy to get LSPS1-2 on the correct state to check this (yet).
Other tangential work is happening that will make this easier and more tests will come in the near future.

A few decisions that could be changed:

- the DOS protections are optional (default true). maybe this should not be configurable?
- I made the dos_protection_enforcer generic enough so it would be possible to add more behavior in the future. also some logic could be moved here like the ignored_peers logic from the manager, which could make sense to move to the dos enforcer

thoughts @tnull ?

@ldk-reviews-bot
Copy link

ldk-reviews-bot commented Aug 7, 2025

👋 Thanks for assigning @tnull as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@martinsaposnic martinsaposnic mentioned this pull request Aug 7, 2025
18 tasks
Copy link

codecov bot commented Aug 7, 2025

Codecov Report

❌ Patch coverage is 91.48936% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.91%. Comparing base (3b939c0) to head (aab6332).

Files with missing lines Patch % Lines
lightning-liquidity/src/lsps2/service.rs 89.06% 6 Missing and 1 partial ⚠️
lightning-liquidity/src/lsps5/service.rs 91.66% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3993   +/-   ##
=======================================
  Coverage   88.91%   88.91%           
=======================================
  Files         174      174           
  Lines      125066   125159   +93     
  Branches   125066   125159   +93     
=======================================
+ Hits       111197   111291   +94     
- Misses      11358    11359    +1     
+ Partials     2511     2509    -2     
Flag Coverage Δ
fuzzing 22.07% <0.00%> (-0.02%) ⬇️
tests 88.74% <91.48%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tnull tnull requested review from tnull and removed request for joostjager August 7, 2025 17:53
@ldk-reviews-bot
Copy link

🔔 1st Reminder

Hey @tnull! This PR has been waiting for your review.
Please take a look when you have a chance. If you're unable to review, please let us know so we can find another reviewer.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Thanks for looking into this!

use lightning::sign::NodeSigner;

/// A trait for implementing Denial-of-Service (DoS) protection mechanisms for LSP services.
pub trait DosProtectionEnforcer {
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO this is over-engineering. We can just have a single method on LSPS5ServiceHandler that checks the necessary bools. I also don't think this warrants to be in a separate module right now.

peer_state
.outbound_channels_by_intercept_scid
.values()
.any(|c| c.is_pending_channel_open())
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, we'll also want to allow LSPS5 for any activity beyond this point, no? I think rather than adding helpers for each individual state, maybe we finally should expose the list of OutboundJITChannelStates at least pub(crate) and also implement Ord for OutboundJITChannelState? This would allow us to use comparison operators on the channel states.

Also, can we somehow enforce that for a pending payment, we'd always first process it and advance the LSPS2 state before evaluating whether we can notifiy the LSPS5 client?

Copy link
Contributor Author

@martinsaposnic martinsaposnic Aug 11, 2025

Choose a reason for hiding this comment

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

what I thought here (and I didn't clarify, sorry) is that it should only check for the pending_channel_open. after that state, the channel will be opened and the LSPS5 service has_active_channels check will kick in, so there is no point on checking the other LSPS2 states. does that make sense?

if !self.peer_is_engaged(sender_node_id) {
return Err(LightningError {
err: format!(
"Rejecting LSPS5 request from {:?} without existing engagement",
Copy link
Contributor

Choose a reason for hiding this comment

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

"existing engagement" is pretty unclear terminology, would be good to find more intuitive wording.

"Rejecting LSPS5 request from {:?} without existing engagement",
sender_node_id
),
action: ErrorAction::IgnoreAndLog(Level::Info),
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't log on on Info here.

@@ -64,6 +64,8 @@ struct StoredWebhook {
pub struct LSPS5ServiceConfig {
/// Maximum number of webhooks allowed per client.
pub max_webhooks_per_client: u32,
/// Require an existing channel or active LSPS1/LSPS2 flow before accepting requests.
pub enforce_dos_protections: bool,
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's at least start out with enabling this by default, i.e., don't make it configurable.

@ldk-reviews-bot
Copy link

👋 The first review has been submitted!

Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer.

When handling an incoming LSPS5 request, the manager will check
if the counterparty is 'engaged' in some way before responding.
`Engaged` meaning = active channel | LSPS2 active operation | LSPS1 active operation.

Logic: `If not engaged then reject request;`

A single test is added only checking for the active channel condition,
because it's not super easy to get LSPS1-2 on the correct state to check this (yet).
Other tangential work is happening that will make this easier and more tests will come in the near future
@martinsaposnic martinsaposnic force-pushed the lsps5-dos branch 2 times, most recently from 38c8436 to ffe25e6 Compare August 11, 2025 18:50
@martinsaposnic
Copy link
Contributor Author

@tnull thanks for the review! I think this is a much better

I pushed a fixup commit addressing all the comments ffe25e6

I still have my doubts regarding this:

can we somehow enforce that for a pending payment, we'd always first process it and advance the LSPS2 state before evaluating whether we can notifiy the LSPS5 client?

I think that maybe that's not necessary and I posted a question related to that: #3993 (comment)

thanks!

@martinsaposnic martinsaposnic requested a review from tnull August 11, 2025 18:57
- get rid of dos_protection_enforcer. logic will go inside lsps5/service
- improve naming conventions so it's clearer
- do not log on level info for possible spam
- make the dos protections enabled by default
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.

3 participants