Closed
Conversation
We previously set the `htlc_maximum_msat` inside `channel_update` to the channel's capacity, but that didn't make any sense: we will reject htlcs that are above the local or remote `max_htlc_value_in_flight_msat`. We now set this value to match the lowest `max_htlc_value_in_flight_msat` of the channel, and properly type our local value to be a millisatoshi amount instead of a more generic UInt64.
We introduce a new parameter to set `max-htlc-value-in-flight` based on the channel capacity, when it provides a lower value than the existing `max-htlc-value-in-flight-msat` static value.
Channels have a limited number of HTLCs that can be in-flight at a given time, because the commitment transaction cannot have an unbounded number of outputs. Malicious actors can exploit this by filling our channels with HTLCs and waiting as long as possible before failing them (also known as a channel jamming attack). To increase the cost of this attack, we don't let our channels be filled with low-value HTLCs. When we already have many low-value HTLCs in-flight, we only accept higher value HTLCs. Attackers will have to lock non-negligible amounts to carry out the attack.
Member
Author
thomash-acinq
added a commit
that referenced
this pull request
Jul 11, 2025
We do not yet drop HTLCs, the purpose is to collect data first. We add 1. An endorsement bit to `UpdateAddHtlc`. This follows lightning/blips#27. 2. A local reputation system: For each pair (origin node, endorsement value), we compute its reputation as total fees that were paid divided by total fees that would have been paid if all HTLCs had fulfilled. When considering an HTLC to relay, we only forward it if the reputation of its source is higher than the occupancy of the outgoing channel. 3. A limit on the number of small HTLCs per channel. We allow just very few small HTLCs per channel so that it's not possible to block large HTLCs using only small HTLCs (similar to #2330 but continuous).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Channels have a limited number of HTLCs that can be in-flight at a given time, because the commitment transaction cannot have an unbounded number of outputs. Malicious actors can exploit this by filling our channels with HTLCs and waiting as long as possible before failing them (also known as a channel jamming attack).
To increase the cost of this attack, we don't let our channels be filled with low-value HTLCs. When we already have many low-value HTLCs in-flight, we only accept higher value HTLCs. Attackers will have to lock non-negligible amounts to carry out the attack.
I chose to use hard-coded buckets for now that encode the following constraints:
max-htlc-value-in-flightmax-htlc-value-in-flightmax-htlc-value-in-flightmax-htlc-value-in-flightI don't know if we should make it configurable. It's quite hard to reason about the effectiveness of a specific configuration, it really needs to be plotted against various channel configurations, as the
reject htlcs when buckets are fullunit test does.When channels are very big, we probably never expect HTLCs bigger than 1% to be relayed: instead of using a percentage of
max-in-flight, should we use something non-linear here, such as a percentage off(max-in-flight)with a carefully chosen non-linear functionf?Assuming
anchor_outputs_zero_fee_htlc_txsand a dust limit of330 sat, the current configuration offers the following guarantees:max-htlc-value-in-flight = 500_000 satandmax-accepted-htlcs = 30:4_950 satto block HTLCs below 5_000 sat`49_950 satto block HTLCs below 25_000 sat`124_950 satto block HTLCs below 50_000 sat`274_950 satto jam the channel entirelymax-htlc-value-in-flight = 1_000_000 satandmax-accepted-htlcs = 30:4_950 satto block HTLCs below 10_000 sat`94_950 satto block HTLCs below 50_000 sat`244_950 satto block HTLCs below 100_000 sat`544_950 satto jam the channel entirelymax-htlc-value-in-flight = 5_000_000 satandmax-accepted-htlcs = 30:4_950 satto block HTLCs below 50_000 sat`454_950 satto block HTLCs below 250_000 sat`1_204_950 satto block HTLCs below 500_000 sat`2_704_950 satto jam the channel entirelymax-htlc-value-in-flight = 500_000 satandmax-accepted-htlcs = 50:8_250 satto block HTLCs below 5_000 sat`83_250 satto block HTLCs below 25_000 sat`208_250 satto block HTLCs below 50_000 sat`458_250 satto jam the channel entirelymax-htlc-value-in-flight = 1_000_000 satandmax-accepted-htlcs = 50:8_250 satto block HTLCs below 10_000 sat`158_250 satto block HTLCs below 50_000 sat`408_250 satto block HTLCs below 100_000 sat`908_250 satto jam the channel entirelymax-htlc-value-in-flight = 5_000_000 satandmax-accepted-htlcs = 50:8_250 satto block HTLCs below 50_000 sat`758_250 satto block HTLCs below 250_000 sat`2_008_250 satto block HTLCs below 500_000 sat`4_508_250 satto jam the channel entirelyI'm not sure how we could plot this to make it easier to analyze, there are quite a lot of moving parameters to take into account...
An important caveat of this bucketing strategy is how it interacts with balance estimation. If our first bucket is full, a sender may think our balance is low and will avoid sending large HTLCs through the channel, while this large HTLC could be forwarded. This means that the balance estimation feature would actually defeat the congestion control mechanism...
We may need to return a dedicated onion error when applying such congestion control to ensure that senders don't misinterpret it as a liquidity failure, but then every relaying node has an incentive to use that new error to avoid revealing that they have a liquidity failure ¯_(ツ)_/¯
NB: this PR builds on top of #2299