Skip to content

Conversation

carlaKC
Copy link
Contributor

@carlaKC carlaKC commented Aug 13, 2025

This PR replaces #1071 with our latest proposal for a reputation scheme and resource management to mitigate slow jamming attacks.

Key differences for the new approach are:

  • Reputation is tracked for outgoing channels, as only downstream peers are capable of slowing down resolution of htlcs.
  • Resources are limited on incoming channels, as we're defending against a malicious downstream peer slowing resolution down.
  • We now pass an accountable signal to our outgoing peer to indicate whether we'll hold their reputation accountable because scarce resources have been used (rather than an endorsed signal that indicates scarce resources may be used).
  • This signal originates at the receiving node, as they're ultimately in charge of resolution time, and is propagated in the onion to prevent tampering.
  • General bucket resources have some DOS protections, and we introduce a congestion bucket to help facilitate honest payments during attack.

Copy link
Contributor

@morehouse morehouse left a comment

Choose a reason for hiding this comment

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

Concept ACK.

This is a big improvement over #1071:

  • The inverted reputation scheme protects against sink attacks.
  • The inverted reputation scheme enables new nodes to immediately send payments to reputable destinations when the network is under attack (better UX).
  • The hash-based general slot assignment provides some discouragement for general jamming attacks.
  • The tit-for-tat congestion bucket provides a mechanism to build reputation even when being general-jammed.

We probably also want to tweak the general slot assignment recommendations a bit to improve the strength of the hash-based defense:

  • Scale general_bucket_slot_count based on max_accepted_htlcs instead of the commitment type.
  • Increase the recommended percentage of total slots allocated to the general bucket.

There's also still an open question about how to address the issues with high payment fanout. For example, LSPs often have high payment fan-out, which would prevent their clients from ever getting a good reputation with the current algorithm.

Comment on lines +202 to +206
Reputation relies on the fees charged by the local node rather than the fee
offered by the sender to prevent over-payment of advertised fees from
contributing to reputation. This is unlikely to impact honest senders who will
abide by advertised fee policies, and complicates (but does not prevent)
attempts to artificially inflate reputation through excessive fee payments.
Copy link
Contributor

Choose a reason for hiding this comment

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

Why should we care about reputation inflation if it's paid for? Fees paid are sunk costs to an attacker regardless of whether they happen in one payment or many.

Basing reputation on charged rather than offered fees could be good for a different reason though -- to avoid certain reputation destruction attacks. Suppose payments like this: M1 -> A -> B -> M2, where the attacker is trying to destroy B's reputation with A. The attacker could offer insanely high fees to A and minimal fees to B, then hold HTLCs until expiry. As a result, B's reputation would drop way more than M2's.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why should we care about reputation inflation if it's paid for?

We generally don't want an attacker to be able to take unusual actions that give them an advantage over honest behaving nodes. This just forces an attacker to align more with the behavior of honest nodes, even if it's trivial for them to do (multiple payments).

to avoid certain reputation destruction attacks

Good point! I'll include this in rationale.

Comment on lines 356 to 361
A HTLC is eligible to use the general bucket if for its
`(incoming scid, outgoing scid)`'s assigned resources:
- Currently occupied slots < `general_bucket_slot_count`
- Currently occupied liquidity + `amt_msat` <= `general_bucket_liquidity_allocation`
Copy link
Contributor

Choose a reason for hiding this comment

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

We should explicitly state that the general bucket can only be used when one of the assigned slots for that channel pair is unoccupied.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is covered by the general_bucket_slot_count check above?
It's badly named, I"ll change it to general_bucket_slot_allocation so that it's clearer.

Comment on lines +372 to +376
it difficult for an attacker to crowd out honest traffic. With these defaults,
an attacker will need to open approximately 50 channels in expectation to gain
access to all general resources.
Copy link
Contributor

Choose a reason for hiding this comment

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

As noted above, the number of channels required is a lot less with typical values for max_accepted_htlcs. If we have 16 general slots, we'd expect them to be fully occupied after ~11 or ~3 opened channels for general_bucket_slot_counts of 5 and 20, respectively (coupon collector expectation).

average, expressed in seconds.
- `decaying_average`: stores the value of the decaying average.
- `decay_rate`: a constant rate of decay based on the rolling window chosen,
calculated as: `((1/2)^(2/window_length_seconds))`.
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 provide a recommendation for window_length_seconds?

It looks like this will decay by 75% every window, so I'm guessing the same size as the fixed window would be reasonable here...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

window_length_seconds is a placeholder for the period that you want a rolling average over - for revenue you'd use revenue_window and for reputation you'd use revenue_window * reputation_multiplier, for example.

I'll update the wording - let me know if it clarifies it!

@carlaKC carlaKC force-pushed the outgoing-reputation branch from 1bab2ce to c25f7b1 Compare September 25, 2025 13:31
@carlaKC
Copy link
Contributor Author

carlaKC commented Sep 25, 2025

Thanks for taking a look @morehouse! Addressed some of your feedback - diff here.

We probably also want to tweak the general slot assignment recommendations a bit to improve the strength of the hash-based defense

Agreed, this could definitely use some refining! Also interested to see whether we can think about larger default max_accepted_htlcs with this mitigation in place - will bring it up in the next spec meeting.

There's also still an open question about how to address the issues with high payment fanout. For example, LSPs often have high payment fan-out, which would prevent their clients from ever getting a good reputation with the current algorithm.

Now that we look at reputation only in the outgoing direction, a sending LSP wouldn't need to worry about its clients (only the node it is forwarding out to). For a receiving LSP that needs to decide whether the client that they're sending a HTLC to has reputation, I imagine there are some LSP-related heuristics or different set of parameters that they could use?

Comment on lines 339 to 341
- `general_bucket_slot_count`:
- If the channel type allows a maximum of 483 HTLCs: 20
- If the channel type allows a maximum of 120 HTLCs: 5
Copy link
Contributor

Choose a reason for hiding this comment

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

LND and LDK both have defaults on 483, so most of the network is probably running with this default.

That's true for LND, though it looks like LDK defaults to 50.

Assuming that these values are set to protect nodes from the on-chain cost of resolution, I'm hopeful that other impls will be able to increase the number of slots they allow now that they can't be so trivially filled up.

Good point. Using the suggested defaults with zero-fee commits would put the general bucket size at 120 * .4 = 48, which is essentially the same as current max_accepted_htlcs defaults. Usage above that amount would be possible but would require reputation.

1. type: 0 (`blinded_path`)
2. data:
* [`point`:`path_key`]
1. type: 1 (`accountable`)

Choose a reason for hiding this comment

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

Why not allow multiple accountability level instead of it being just a boolean flag? Just as with the endorsement before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The accountable signal being set should be interpreted as: "I will hold your reputation responsible for the timely resolution of this HTLC", which is a boolean statement.

By contrast, endorsement signals meant "I think that this HTLC will resolve in a timely manner", which makes more sense to interpret as a range.

The recipient of a payment is ultimately responsible for the fast
resolution of a payment (though intermediate node can, of course,
slow it down).

We add a signal from the final recipient that the sender can use to
reason about the amount of time it should take to resolve.
Once we have a signal from the recipient, we need a way to propagate
this information throughout the route. Including a signal in the onion
provides an uncorruptable way for the sender to propagate this signal.
If the sender is dishonest, this will eventually be detected by the
final recipient.

In the commits that follow we'll add reputation and resource management
that will allow nodes to use this signal to protect against jamming
attacks.
@carlaKC carlaKC force-pushed the outgoing-reputation branch from c25f7b1 to 84ab329 Compare October 6, 2025 16:45
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