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

Hold fees #843

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions 02-peer-protocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,14 @@ A fulfilling node:
transaction, AND is past this fulfillment deadline:
- MUST fail the channel.

### Hold fees

To prevent abuse of the network, nodes will charge a fee for the time that their money is locked up in htlcs. The general direction of this fee stream is backwards. Every node along the route that receives an htlc will pay a time-dependent hold fee rate to its predecessor when the htlc resolves, regardless of whether the htlc was fulfilled or failed. The hold fee rate increases with every hop downstream, because more and more money gets locked up. An intermediary or final node that holds on to the htlc for an unreasonably long period of time will need to pay for that (indirectly) to every node upstream.

Forwarding an htlc will always add some delay. To prevent senders from collecting 'free' hold fees, routing nodes will demand a discount on the hold fee. If the routing node forwards swiftly, this discount will turn the hold fee negative and require the sender to pay. Ultimately the sum of all these discounts is paid by the original sender of the payment. This is a protection against spam.

Hold fees only exist in the off-chain domain and don't materialize for pending htlcs on the commitment transaction when a channel is force-closed. There is no way to negotiate the correct hold fee based on the actual hold time when the commitment goes to chain. The assumption is that this is acceptable because the chain fees for the commitment and 2nd level transactions act as an anti-DoS measure already.

### Adding an HTLC: `update_add_htlc`

Either node can send `update_add_htlc` to offer an HTLC to the other,
Expand All @@ -805,6 +813,8 @@ is destined, is described in [BOLT #4](04-onion-routing.md).
* [`sha256`:`payment_hash`]
* [`u32`:`cltv_expiry`]
* [`1366*byte`:`onion_routing_packet`]
* [`u64`:`hold_fee_rate_day`]
Copy link
Contributor

Choose a reason for hiding this comment

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

why not per block?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've chosen time because it matches the actual cost of locked funds closely. Blocks probably works just as well though.

* [`u64`:`hold_fee_discount`]

#### Requirements

Expand All @@ -829,6 +839,8 @@ A sending node:
its commitment transaction, it cannot pay the fee for the updated local or
remote transaction at the current `feerate_per_kw` while maintaining its
channel reserve.
- SHOULD NOT offer a combination of `amount_msat`, `cltv_expiry`, `hold_fee_rate_day` and `hold_fee_discount` such that the remote node cannot pay the hold fee for the longest possible hold duration. The longest possible hold duration is the `cltv_expiry` delta in blocks multiplied by ten minutes. This must also take into account all currently outstanding htlcs.
Copy link
Contributor

Choose a reason for hiding this comment

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

See the above comment. It seems strange to do this artificial conversion between wall clock time and block time when we already have cltv_expiry as the upper hold duration and a perfect time stamping server on the base layer.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, agreed that this causes friction for the calculation of the max hold fee.

- SHOULD NOT offer a `hold_fee_discount` that it cannot pay for. This must also take into account all currently outstanding htlcs.
- MUST offer `amount_msat` greater than 0.
- MUST NOT offer `amount_msat` below the receiving node's `htlc_minimum_msat`
- MUST set `cltv_expiry` less than 500000000.
Expand Down Expand Up @@ -916,6 +928,7 @@ To supply the preimage:
* [`channel_id`:`channel_id`]
* [`u64`:`id`]
* [`32*byte`:`payment_preimage`]
* [`u64:hold fee`]

For a timed out or route-failed HTLC:

Expand All @@ -925,6 +938,7 @@ For a timed out or route-failed HTLC:
* [`u64`:`id`]
* [`u16`:`len`]
* [`len*byte`:`reason`]
* [`u64:hold fee`]

The `reason` field is an opaque encrypted blob for the benefit of the
original HTLC initiator, as defined in [BOLT #4](04-onion-routing.md);
Expand All @@ -940,6 +954,7 @@ For an unparsable HTLC:
* [`u64`:`id`]
* [`sha256`:`sha256_of_onion`]
* [`u16`:`failure_code`]
* [`u64:hold fee`]

#### Requirements

Expand All @@ -950,6 +965,7 @@ A node:
commitment transactions:
- MUST NOT send an `update_fulfill_htlc`, `update_fail_htlc`, or
`update_fail_malformed_htlc`.
- MUST set `hold_fee` to the hold fees that it owes the sending node. Let `hold_duration_days` be the actual time that the htlc was held, expressed in days. This value is calculated as `hold_fee_rate_day` (from `update_add_htlc`) * `hold_duration_days` - `hold_fee_discount` (also from `update_add_htlc`). Example: `hold_fee_rate_day`=200, `hold_fee_discount`=3, `hold_duration_days`=0.02 (30 minutes). Then `hold_fee` is 200 * 0.02 - 3 = 1 sat. `hold_fee` can be negative in which case the sending node owes the receiving node.
Copy link
Contributor

Choose a reason for hiding this comment

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

in the case of minutes it is easy to compute the hold_duration_days but in reality there will be seconds and even milliseconds involved thus my repeated suggestion to stick with a hold_duration_blocks as the number of blocks since the htlc was offered and the current hight. It might still be tricky to negotiate the hold_duration_blocks if a new block is just being propagated. Nodes might either allow a grace period of a couple seconds or we need some mechanism to renegotiate this value.

Copy link
Collaborator Author

@joostjager joostjager Dec 14, 2021

Choose a reason for hiding this comment

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

Indeed, either way there must be a grace period. For regular lightning payments, there is a similar grace delta added by the sender to accommodate for blocks that are produced while the payment is in flight.

A per-block rate doesn't have the granularity of a per-time rate, but perhaps that isn't a problem. To combat spam, the minimum hold fee that the sender needs to pay to each node must be sufficiently high. Charging for a second of hold time is probably not enough, if it is even possible to express in msat.


A receiving node:
- if the `id` does not correspond to an HTLC in its current commitment transaction:
Expand All @@ -967,6 +983,8 @@ A receiving node:
- MUST return an error in the `update_fail_htlc` sent to the link which
originally sent the HTLC, using the `failure_code` given and setting the
data to `sha256_of_onion`.
- MUST fail the channel if `hold_fee` is more than 1% below the expected value. This tolerance exists to accommodate for clock skew.
- MUST account for the `hold_fee` internally by adding the value to its balance and subtracting the value from the remote balance.

#### Rationale

Expand Down
9 changes: 8 additions & 1 deletion 04-onion-routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,10 @@ It is formatted according to the Type-Length-Value format defined in [BOLT #1](0
2. data:
* [`32*byte`:`payment_secret`]
* [`tu64`:`total_msat`]
1. type: 10 (`hold_fee`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't that conflict with onion messages in #759

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This PR is only conceptual at this stage.

2. data:
* [`u64`:`hold_fee_rate_day`]
* [`u64`:`hold_fee_discount`]

### Requirements

Expand All @@ -271,16 +275,19 @@ The writer:
- MUST use the legacy payload format instead.
- For every node:
- MUST include `amt_to_forward` and `outgoing_cltv_value`.
- MUST include `hold_fee`
- For every non-final node:
- MUST include `short_channel_id`
- MUST NOT include `payment_data`
- MUST set `hold_fee_rate_day` so that difference between incoming and outgoing `hold_fee_rate_day` for the receiving node is at least the expected value based on the receiving node's channel policy.
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not understand this sentence and thus not the requirement. Is that related to hold_fee_rate_ppm_day from BOLT 7 if so why not including hold_fee_rate_base_day? (while I probably just missed some point here I of course iterate that I would set those rates in blocks instead of days)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

A node is paying a hold fee to the next node and receiving a hold fee from its predecessor. The idea of this sentence is to point out that a node must make sure that the difference between fees paid and fees received covers the hold fee that they require for themselves.

Similar to forwarding a lightning payment where nodes must make sure that the difference between incoming and outgoing amount is at least their desired routing fee.

- MUST set `hold_fee_discount` to the amount that the reading node would owe its predecessor if the htlc would remain locked for `hold_grace_period_sec` (as advertised by the reading node), plus all amounts owed by nodes further downstream to their predecessors if they'd all hold the htlc for their `hold_grace_period_sec`.
- For the final node:
- MUST NOT include `short_channel_id`
- if the recipient provided `payment_secret`:
- MUST include `payment_data`
- MUST set `payment_secret` to the one provided
- MUST set `total_msat` to the total amount it will send

- MUST set `hold_fee_discount` to the amount that the reading node would owe its predecessor if the htlc would remain locked for `hold_grace_period_sec` (as advertised by the reading node).
The reader:
- MUST return an error if `amt_to_forward` or `outgoing_cltv_value` are not present.
- if it is the final node:
Expand Down
6 changes: 6 additions & 0 deletions 07-routing-gossip.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,9 @@ of *relaying* payments, not *sending* payments. When making a payment
* [`u32`:`fee_base_msat`]
* [`u32`:`fee_proportional_millionths`]
* [`u64`:`htlc_maximum_msat`] (option_channel_htlc_max)
* [`u64`:`hold_fee_rate_base_day`]
* [`u64`:`hold_fee_rate_ppm_day`]
* [`u64`:`hold_grace_period_sec`]

The `channel_flags` bitfield is used to indicate the direction of the channel: it
identifies the node that this update originated from and signals various options
Expand Down Expand Up @@ -485,6 +488,9 @@ The origin node:
- MUST set `fee_proportional_millionths` to the amount (in millionths of a
satoshi) it will charge per transferred satoshi.
- SHOULD NOT create redundant `channel_update`s
- SHOULD set `hold_grace_period_sec` to the total processing time that it needs for forwarding an htlc over this channel. This includes both the forward (`update_add_htlc`) and the backward pass (`update_fulfill_htlc` / `update_fail_htlc`). As long as this node's delay stays within the grace period, there won't be any hold fee to pay.
- MUST set `hold_fee_rate_base_day` to the base fee per day that it expects to get paid via its outgoing link for having the htlc in flight.
- MUST set `hold_fee_rate_ppm_day` to the proportional fee (in parts per million) per day that is expects to get paid back via its outgoing link for having the htlc in flight. Example: `hold_fee_rate_base_day` = 10, `hold_fee_rate_ppm_day` = 1000, htlc amount = 2000000 sat, hold duration = 1 hour. The node will then expect to get paid (10 + 2000000 * 1000000 / 1000) / 24 = 83.75 sat in hold fees.

The receiving node:
- if the `short_channel_id` does NOT match a previous `channel_announcement`,
Expand Down