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

routing: inbound fees send support #6934

Merged
merged 4 commits into from Mar 31, 2024

Conversation

joostjager
Copy link
Collaborator

@joostjager joostjager commented Sep 21, 2022

Implements send support for inbound routing fees. The receiver side is implemented in #6703 and this PR is based off of that.

Implementation details

  • The channel_update extra opaque data isn't validated before it was stored in the database. This means that everywhere we read this data, a tlv error might be encountered. In this PR, this case is handled in a mostly defensive way, skipping over the channel policy.

Based on #6703. Review first commits there.

Support for inbound fees in BuildRoute is explored in #7060.

@joostjager
Copy link
Collaborator Author

Removed exit hop inbound fees. Previous code saved in master...bottlepay:lnd:inbound-fees-send-support-exit-hop

@joostjager joostjager force-pushed the inbound-fees-send-support branch 3 times, most recently from fc549fa to f9894dc Compare October 25, 2022 10:58
@saubyk saubyk added the inbound fee Changes related to inbound routing fee label Nov 9, 2022
@saubyk saubyk added this to the v0.17.0 milestone Nov 9, 2022
signedFee := inboundFee + outboundFee
fee := lnwire.MilliSatoshi(0)
if signedFee > 0 {
fee = lnwire.MilliSatoshi(signedFee)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@halseth you expressed concerns about negative fees being able to cancel out probability penalties for unreliable nodes. I've addressed the issue here by always rounding the fee that is used for the edge weight up to zero.

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Perhaps this check must be removed now that the node total fee is guarded against negative. Otherwise pathfinding may return suboptimal routes?

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 am actually quite sure of this. Suppose a node has an in fee of -100000 and an out fee of 100000. Those cancel out perfectly and shouldn't lead to pathfinding that is any different from a zero fee?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But... we still need to do this because dijkstra can't handle negative weights 🤷

It is a possibility though to floor at zero the actual edge weight calculated below rather than the fee.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am actually quite sure of this. Suppose a node has an in fee of -100000 and an out fee of 100000. Those cancel out perfectly and shouldn't lead to pathfinding that is any different from a zero fee?

Do you mean that those fees are on a single channel or on a node with incoming and outgoing channels?

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 mean on a node with incoming and outgoing channels. The fee paid to that particular node would be 0, so you'd expect it to be treated no different from a node that actually charges zero fee.

Copy link
Collaborator

Choose a reason for hiding this comment

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

This could still be a problem, looking into that. edit: left a main comment #6934 (review)

Comment on lines 790 to 793
totalFee := toNodeDist.outboundFee +
lnwire.MilliSatoshi(inboundFee)

if lnwire.MilliSatoshi(totalFee) > r.FeeLimit {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we still need the total fees for the r.FeeLimit check. Also, lnwire.MilliSatoshi has underlying type of uint64, so it wouldn't work for negative inbound fees. So we would keep the previous code here and use fee := lnwire.MilliSatoshi(int64(toNodeDist.outboundFee) + inboundFee), which is guaranteed to be non-negative?

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 think further up, it is already enforced that in > -out. These casts don't really matter I think, because on the lowest level these data types are the same?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This checks only the hop fee, not the total fee accrued over the path, but the fee limit is for the whole path, I think.

routing/pathfind.go Outdated Show resolved Hide resolved
Copy link
Collaborator

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

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

I think that this PR is in really good shape. One issue that came up during previous reviews was that there is a difficulty in computing optimal fees when using a zero fee cutoff for the total channel fee. The cutoff needs to be applied to avoid that channel weights become negative due to negative inbound fees, which otherwise poses a problem for Dijkstra's algorithm. The non-optimality can be seen in this example:

Two competing routes (respecting total node fees to be non-negative)
...-A-[out fee 0 msat | in fee -100 msat]-B-[out 100 msat | in fee 0]-C
and
...-A'-[out fee 100 msat | in fee -100 msat]-B-[out 100 msat | in fee 0]-C
are assigned a virtual cost of 100 msat in pathfinding because of the restriction that total fees per channel are not allowed to become negative (the first hops have zero virtual fees). Both routes are then equivalent, any one may be probabilistically selected over the other. The first one in reality is 100 msat cheaper than the second one, leading to potential overpayment by a sender if the second one is used. The problem arises when B sets their inbound fees to negative values, larger in magnitude than the outbound fees charged by A. B doesn't earn anything in this process here, but they subsidize A'. B has an incentive to enforce the total channel fee by decreasing their inbound discount to 0 msat, because then they can earn the full 100 msat on the A-B-C route, without making the path more expensive in terms of virtual fees.

@joostjager solved this by mapping from the node-based topology to a line graph (https://en.wikipedia.org/wiki/Line_graph), where the pivots of exploration are not the nodes, but the points in between them. The effect of this approach is that distances are tracked on a peer-tuple basis rather than on a per node basis, which I think solves the problem of negative (inbound) fees in conjunction with non-negatively constrained node total fees for Dijkstra's algorithm. This last commit is a larger conceptual change, where we need to be sure that no side effects are introduced, which is why we could start with the non-optimal cutoff solution first (as imo it's incentive compatible) and introduce the exact approach at a later stage, but would be open for the complete solution as well.

@@ -240,11 +251,13 @@ func (c *GraphCache) UpdatePolicy(policy *models.ChannelEdgePolicy, fromNode,
// policy for node 1.
case channel.IsNode1 && edge1:
channel.OutPolicySet = true
Copy link
Collaborator

Choose a reason for hiding this comment

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

unrelated to this PR, do you know why OutPolicySet this is needed?

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 traced back a bit and it seems it is a left-over from old times when in this function the channel id was retrieved from the out policy object. #3749 introduced the check when it still seemed to make sense.

@@ -956,7 +1017,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
}

edge := edgeUnifier.getEdge(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we need to make this dependent on the previous outbound fee as well? We may have two parallel policies [in: -150 msat, out: 200msat] and [in: 0 msat, out: 100 msat] with previous outbound fee of 0 msat. We would take the second policy right now, but it's actually the first policy that leads to more fees because of the total node fee cutoff.

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 don't think that parallel policy logic is all that essential, because node operators should know that ambiguous policies may lead to failures. But nevertheless you are right. I am now passing in nextOutFee to edgeUnifier.

routing/unified_edges_test.go Outdated Show resolved Hide resolved
routing/pathfind.go Outdated Show resolved Hide resolved
@joostjager
Copy link
Collaborator Author

I am in favour of the non-optimal cutoff solution for now. I think it is sufficiently good to gather feedback on the inbound fees concept, and - as you say - we can revisit node pair search at a later stage. I've archived the relevant commit on a different branch https://github.com/joostjager/lnd/tree/inbound-fees-node-pair-search.

Copy link

coderabbitai bot commented Jan 24, 2024

Important

Auto Review Skipped

Auto reviews are limited to the following labels: llm-review. Please add one of these labels to enable auto reviews.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository.

To trigger a single review, invoke the @coderabbitai review command.

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

@joostjager joostjager force-pushed the inbound-fees-send-support branch 3 times, most recently from 96903da to ca5dd90 Compare January 24, 2024 11:27
Copy link
Collaborator

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

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

LGTM 🎉 (tested pathfinding in a small network as well).

@joostjager
Copy link
Collaborator Author

Rebased

@lightninglabs-deploy
Copy link

@joostjager, remember to re-request review from reviewers when ready

Copy link
Member

@Roasbeef Roasbeef left a comment

Choose a reason for hiding this comment

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

LGTM 🪞

Considering if we should actually also have a hard reject on the RPC level if users try to set a positive inbound fee. In the future we could lift it given sufficient uptake within the core routing network. We can also add more telemetry using the HTLC interceptor (I think we're missing the field there actually?), which'll allow node operators to log the % of incoming payments that are able to observe their negative inbound fees.


fee = int64(outboundFee) + inboundFee
if fee < 0 {
fee = 0
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps we should log these occurrences on the Tracef log level?

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 am not sure. The zero-floor has now become a feature of inbound fees both on the receiver and sender side. Nodes might use that and it doesn't necessary indicate a configuration mistake - if that was the reason you had in mind for logging?

@Roasbeef
Copy link
Member

Needs rebase + conflicts addressed.

Preparation so that we can have the inbound fee available
in addition to the outgoing policy.
Add sender-side support for inbound fees in pathfinding
and route building.
@Roasbeef Roasbeef merged commit 5599b3c into lightningnetwork:master Mar 31, 2024
26 of 27 checks passed
@twofaktor
Copy link
Contributor

twofaktor commented Mar 31, 2024

Will be possible to set the default inbound fees on lnd.conf the same as the existing outbound fees? I can't see any modifications related to this new feature and merged PR in the sample-lnd.conf available on the repo, and any related with this: https://github.com/lightningnetwork/lnd/blob/master/sample-lnd.conf#L626-L633

@Roasbeef
Copy link
Member

Roasbeef commented Apr 1, 2024

@twofaktor that makes a lot of sense, I've made a tracking issue for your request here: #8610. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
inbound fee Changes related to inbound routing fee P1 MUST be fixed or reviewed path finding routing
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

8 participants