lightning / blips

Bitcoin Lightning Improvement Proposals
132 stars 46 forks source link

Inbound routing fees #18

Open joostjager opened 2 years ago

joostjager commented 2 years ago

The Lightning BOLTs define a routing fee that is based on the policy of the outgoing channel. This makes it impossible for routing node operators to differentiate in fees between incoming channels.

Incoming traffic from some peers may be more desired than from others, for example because it balances out traffic in the opposite direction.

This bLIP defines an optional channel_update message field to express an inbound fee discount. Senders who are able to read this field can benefit from lower routing fees using those channels.

Alternative: pairwise fees

An alternative scheme for inbound fees are the so-called pairwise fees, where a distinct fee can be set for each combination of incoming and outgoing channel. This can also be implemented as a discount to remain backwards compatible.

For this BLIP, I chose to start simple with a plain single inbound fee. A single fee only requires trivial changes to pathfinding algorithms and there is no potential for quadratic growth of gossip data.

If pairwise fees prove to be required, an additional BLIP can be defined. It can again be formulated as another discount on the general inbound discount, to keep all options open for implementers.

Additional information

TheBlueMatt commented 2 years ago

IMO this should not go in the channel_update message, but rather a new point-to-point message to tell the counterparty to decrease their outbound fee by this much. That way you don't have (as much of a) chicken and eh problem when routing, but rather can let individual peer pairs upgrade and then routing nodes will all get the discount.

joostjager commented 2 years ago

I don't see how that achieves the same goal. I am looking to make senders aware of the discount so that they can choose routes that are better for them. How will a sender become aware of that new p2p message if it is only exchanged between two remote nodes?

TheBlueMatt commented 2 years ago

A channel participant can inform their counterparty that they wish to take a negative inbound fee for the channel, then their counterparty can subtract the given inbound fee from their announced outbound fees, which all existing nodes will honor. That way no one except the two channel participants needs to update any software at all. As-is, I think its a bit of a stretch to suggest this has no broad-upgrade implication.

joostjager commented 2 years ago

Interesting idea. I suppose it's up to your counterparty to actually lower their outbound fee and not something that can be enforced. Which simplifies the implementation.

What's left is just the ability to tell your peer to send you fewer sats.

It may be a problem for final nodes though that also route. If they are accepting a payment as the final destination, they'll also receive less and this can't be offset by an outgoing fee. The counterparty doesn't know which position in the route they are.

TheBlueMatt commented 2 years ago

I suppose it's up to your counterparty to actually lower their outbound fee and not something that can be enforced.

Sure, they could also increase their fee corresponding to your negative fees in the current implementation too :).

It may be a problem for final nodes though that also route. If they are accepting a payment as the final destination, they'll also receive less and this can't be offset by an outgoing fee.

Hmm, yea, neither the existing nor the new proposal really properly lets you assign positive fees on inbound edges (by offsetting negative fees with outbound fee increases) for this reason, I suppose. I guess the "easy" way to fix that is to use something like phantom nodes and assign the fee offset to the fake hop.

joostjager commented 2 years ago

Well in the existing proposal, the sender is aware of the final hop. This BLIP says that no inbound fee needs to be paid to the final node, and the sender can take that into account when building the route.

Yes, a fake node can be used to let the payer pay for the discount they got :)

With the new p2p proposal, it may not be necessary anymore to do negative fees. That was just for backwards compatibility. I think it can be reformulated to positive fees. Basically forcing your peer to share part of their outbound fee with you.

For the final node, this would be a little better I guess because at least your getting the invoice amount.

TheBlueMatt commented 2 years ago

With the new p2p proposal, it may not be necessary anymore to do negative fees. That was just for backwards compatibility. I think it can be reformulated to positive fees. Basically forcing your peer to share part of their outbound fee with you.

That's true, but it complicates the backwards-compatibility story - why would I ever deploy a change that results in my counterparty increasing the fee on a channel of mine, that just results in less routing revenue for me. Sure, a node may prefer channels with peers that support the new option, but I'm not sure if anyone will prefer that so strongly that they'll reject other new inbound channels.

joostjager commented 2 years ago

I think that not having the ability to charge inbound fees is at this moment already a strong reason for node operators to reject / close certain inbound channels.

I can see this option becoming a requirement for those nodes. The win is that they can keep channels open that they would otherwise close.

TheBlueMatt commented 2 years ago

If you assume very broad deployment of that feature, then yes, I think I'd agree, but in that case I don't think the feature is really a decent candidate for being a bLIP, but rather needs to be a BOLT change :p.

joostjager commented 2 years ago

Need to think about what to do with the insufficient fee failure message. Maybe extend with the channel update message for the incoming channel?

TheBlueMatt commented 2 years ago

I don't think anything needs to change? The first node compares the fee paid according to the onion to what they announced (including the discount). The discount only applies at the update_add_htlc layer, so no changes needed.

joostjager commented 2 years ago

Ah, this was a comment about the channel_update extension proposal. In the p2p solution, nothing like that is needed indeed.

One thing that worries me about the p2p proposal is that it probably requires changes to the channel state machine so that it will be clear from what point onwards exactly the new fee applies.

TheBlueMatt commented 2 years ago

I'm not sure how this differs - either way you have a new argument to your logic that checks an HTLC pays the appropriate fees. There is a slight difference on your peer's side in that they have to subtract the fee amount rather than seeing it in the onion, but that's really rather trivial as well.

I'm kinda unsure why we're arguing so much about this one - one proposal requires network-wide upgrade in order for your fee to be considered (the ultimate goal of anyone providing a fee discount), one does not. Given there was some debate about this being not-widely-deployed that seems like a rather massive advantage.

joostjager commented 2 years ago

I did a PoC for the p2p inbound fee: https://github.com/lightningnetwork/lnd/pull/6878

Overall a fairly straight-forward change. A real functional difference only seems to exist for nodes that are both routing nodes and a payment destination.

A fake hop was suggested in https://github.com/lightning/blips/pull/18#issuecomment-1230505010, but this does add complexity to the setup and it won't be possible to cancel out the discount exactly.

joostjager commented 2 years ago

One downside of the p2p approach is that if the incoming node already charges zero fees, they can't lower their fee any further. So giving them a discount isn't going to make a difference. If there are multiple zero or low fee peers, it won't be possible to have distinct inbound fee schedules for these peers.

TheBlueMatt commented 2 years ago

Right, but in those same cases routing algorithms would need to be modified to support negative fees, which AFAIK no existing node supports. I think negative fees are somewhat of an orthogonal discussion.

joostjager commented 2 years ago

Yes, that's true. Over time, when senders are mostly upgraded, you could switch to positive inbound fees to get around the pathfinding limitation.

Although for p2p this may be uncomfortable for peers, because they need to monitor the inbound fee closely and adjust their fees accordingly to make sure they charge as much as they need to pay p2p to the next node and keep it net zero.

TheBlueMatt commented 2 years ago

Although for p2p this may be uncomfortable for peers, because they need to monitor the inbound fee closely and adjust their fees accordingly to make sure they charge as much as they need to pay p2p to the next node and keep it net zero.

I'm not sure why this is different from today, really? Peers have to watch for channel updates from their peers and track updated routing fees, now they just also have to broadcast their own updates in response to a new message that's similar in nature.

JssDWt commented 2 years ago

I think this is a great proposal. I love the fact that by charging negative inbound fees (and potentially raising the outbound fees) this becomes a backward compatible change.

There's two suggestions I would like to make. 1) Do not allow destination nodes to charge inbound fees (they could charge more for the service they provide instead) 2) More intuitive fee calculation

I think both above points can be addressed by changing the way the total routing fee is calculated.

outbound_fee(amt_to_forward) = inbound_fee.base_msat + outbound_fee.base_msat + (inbound_fee.proportional_millionths + outbound_fee.proportional_millionths) * amt_to_forward / 1e6

Let me give an example. A routing node X charges 100ppm inbound fees from A and 200ppm outbound fees to B. Say X forwards 1,000,000 sats to B. In the proposal in this PR, the fee X gets is calculated as follows:

outbound fee = 1,000,000 * 200ppm = 200 sat
inbound fee = (1,000,000 + 200 sat outbound fee) * 100 ppm = 100.02 sat
total fee = 300.02 sat

The effective ppm is 300.02 ppm. Intuitively though, if a routing node charges 100ppm + 200ppm routing fees you'd expect the fee to be 300ppm.

So I'd suggest calculating the fees over the outgoing amount only. By adding the the inbound + outbound fee policies together and applying that to the outgoing amount:

total fee = 1,000,000 * (100ppm inbound fee + 200ppm outbound fee) = 300 sat

An advantage of this technique is it also addresses point 1). Because since fees are calculated over the outgoing amount. This way routing nodes charge fees for forwards. But a destination node does not charge a fee for receiving a payment. This way you're effectively not charging an inbound fee, but raising/decreasing the outbound fee based on where the payment is coming from. I believe that's more suited to the problem we're trying to solve.

joostjager commented 2 years ago

I like the idea of the more intuitive fee calculation. Because we're talking about fees paid over fees, the difference is small. But it does simplify things on the implementation side. In particular the complicated inverse calculation that can now be dropped. Tested it, and it works without any unexpected challenges in pathfinding.

The other suggestion to not allow payment destinations to charge inbound fees is I think independent of this. For both ways of calculating, we can choose whether to make the final hop inbound-fee-free or not.

Charging inbound fees as a destination is already possible today by setting up a gateway node in front of the final destination. So in that regard there is not much fundamental change.

Perhaps this decision is better informed by looking at routing nodes that are also a payment destination. Suppose they charge inbound fees on some channels for forwards. Would it be desirable for them to charge the same fees if they receive a payment? I'd think that it is beneficial to be able to apply the same traffic shaping as for forwarded traffic, but interested to hear other's opinions.

joostjager commented 2 years ago

So I'd suggest calculating the fees over the outgoing amount only. By adding the the inbound + outbound fee policies together and applying that to the outgoing amount:

total fee = 1,000,000 * (100ppm inbound fee + 200ppm outbound fee) = 300 sat

It's important to note that the policies first need to be added together. If applied separately, rounding differences may occur.

Many route building algorithms work "node by node". For implementation it may be easier to specify the total fee required as inbound_fee + outbound_fee, rounding each component separately.

Another decision to make it is how to round negative fees. A normal integer division rounds towards zero, meaning that positive fees are rounded down and negative fees rounded up. Not sure if this is the most intuitive for negative fees.

Perhaps for all of this, the leading principle should be that senders may always round in their favor.

joostjager commented 2 years ago

~@JssDWt one potential downside of calculation inbound fees based on the downstream outgoing amount is that it may be difficult to use bolt11 route hints as is.~

~If the inbound fee was calculated over the upstream incoming amount, it may have been possible to fold the outbound and inbound fee for a particular channel into a single base fee and fee rate and communicate that to the sender. Not sure though if this is really possible.~

Actually I think it isn't possible to do that.

joostjager commented 2 years ago

@bitromortac suggested a third way to calculate the inbound fees: to base them on the outgoing htlc amount plus the outbound fee.

Example: inbound fee = 1 + 10%, outbound fee = 7 + 3%, outgoing htlc amount = 100

--- 122 ---> (in fee: 12) NODE (out fee: 10) --- 100 --->

In this case, the amount that the inbound fee is based on is 100+10=110. The inbound fee is 1+10%(110)=12.

I've created a Google Sheet that shows the three fee models side-by-side.

The advantage of this latest variation is that:

joostjager commented 2 years ago

Updated PR to reflect newest fee calculation described above.

halseth commented 2 years ago

Regarding charging fees for incoming payments: It seems like this is desirable to enable. If I'm a merchant, I could end up with some incoming channels being used more than others (perhaps because they are towards a well-connected routing node), so it would be nice to incentivise senders to use different channels by giving them a rebate.

joostjager commented 2 years ago

I am not so sure anymore about charging inbound fees for exit hops. Some nodes may not want to do that because it can be perceived as a hidden charge to the payer. If they decide to not set inbound fees, they also can't use the feature to shape forwarding traffic.

It seems most flexible to not charge inbound fees for exit hops. If they then still want it, they can set up a secondary node for payments only or use a virtual channel for example via lnmux.

Thoughts? Wisdom of the crowd: https://twitter.com/joostjgr/status/1582795532115664897

TheBlueMatt commented 2 years ago

Its kinda awkward, but the poll indicates people are split, so I'm not sure there's a "right" answer here - that said, keeping it backwards-compatible (and allowing both positive and negative fees, rather than only negative) seems like a huge win. I'm not sure why it made sense to move away from the "tell your peer to charge more" design - designing a protocol just to make lnd implementation details happy seems like a really strange way to go about it :(.

joostjager commented 2 years ago

The main reason for dropping exit hops fees is that routing nodes that use inbound fees for forwards, can't turn these off if they are a payment destination. And exit hops fees can also be implemented using a virtual/phantom/lnmux channel.

I don't think this is compatible with "tell your peer to charge more", because they'd learn whether they are the second to last node.

joostjager commented 2 years ago

For totally flexibility with regards to the exit hop fees, we can do a follow-up with a new optional channel_update tlv record that defines a base fee and rate specifically for senders to pay to the last hop.

TheBlueMatt commented 2 years ago

I don't buy that that is a sufficient reason to complicate the network more for normal nodes, nor that its worth the restriction that the inbound fees are no longer allowed to be positive. While explicit negative inbound fees are cool, (a) the time required for people to deploy them is annoying, and (b) they are much more limited in use - if you have very, very low (or zero) outbound fees, then you cannot use inbound fees at all (unless you want to pay people to route through you). This is sufficiently limiting that I'm not sure its worth it. If we go the "counterparty adds fee" route we don't have that limitation, and just end up with recipients charging fees. That's a bit awkward, sure, but recipients can already charge fees if they want to, so its not like its a new issue?

joostjager commented 2 years ago

the restriction that the inbound fees are no longer allowed to be positive

Did I accidentally add this restriction somewhere? This is not the plan, the inbound fees can be either negative or positive. But in a transitionary period they will probably be negative.

I should definitely rename the blip though.

If we go the "counterparty adds fee" route we don't have that limitation, and just end up with recipients charging fees. That's a bit awkward, sure, but recipients can already charge fees if they want to, so its not like its a new issue?

They can already do it currently if they want to, but the problem is that they can no longer not charge recipient fees if they don't want to. This is assuming that they do want inbound fees for regular forwards.

TheBlueMatt commented 2 years ago

Did I accidentally add this restriction somewhere? This is not the plan, the inbound fees can be either negative or positive. But in a transitionary period they will probably be negative.

For compatibility with the existing network, explicit inbound fees must always be negative. Senders should always ignore any positive inbound fees as nodes have to accept non-payment to remain compatible. I'm not sure what any sensible deployment strategy looks like here.

joostjager commented 2 years ago

For compatibility with the existing network, explicit inbound fees must always be negative. Senders should always ignore any positive inbound fees as nodes have to accept non-payment to remain compatible. I'm not sure what any sensible deployment strategy looks like here.

Start with negative inbound fees first to learn more about the practical utility of the concept. If a sufficiently large part of the senders takes inbound fees into account, routing nodes may move towards positive inbound fees and accept that they will lose out on routing fees for the remaining senders that ignore those fees.

I don't think it is useful for senders to always ignore positive inbound fees.

TheBlueMatt commented 2 years ago

I don't understand why a sender should choose to pay a higer fee than they have to, which is basically what you're asking them to do.

joostjager commented 2 years ago

If a routing node chooses - at a later stage - to advertise and enforce a positive inbound fee, the sender should pay it. Otherwise their payment attempt will fail.

TheBlueMatt commented 2 years ago

Why would any sender add support for that - if no one supports it no routing node will ever turn it on. Further, defining a new positive routing fee is no longer something that is purely local, but rather is something all nodes need to implement and thus I don't think you can make a compelling argument that it belongs in blips, rather than BOLTs.

joostjager commented 2 years ago

Senders would add support for that if they believe that inbound fees will make the network as a whole better. Maybe the BLIP can be promoted to a BOLT at that point. For now the initial goal is to experiment with negative inbound fees and go from there.

TheBlueMatt commented 2 years ago

Senders would add support for that if they believe that inbound fees will make the network as a whole better.

Sure, a sender would support it if nodes announcing positive inbound fees would reject payments that don't pay it. However, that means nodes can't use positive inbound fees until all senders have been upgraded, probably years, and we'd effectively "hard fork off" all unupgraded senders once that ships. The only way to avoid that is to make it optional for some period of time, which of course would cause senders to not implement it.

Ignoring that and the bLIP/BOLT issue, requiring all senders be upgraded in order to send payments through a node seems like a very huge drawback that we simply dont have if we do the "make your peer increase their fees" approach. That approach is a tiny bit less clean, but doesn't suffer from any of the very substantial drawbacks here, and isn't limited to "negative fees only", which also seems like a major drawback. I just don't understand the dire to go the explicit route at all.

joostjager commented 2 years ago

However, that means nodes can't use positive inbound fees until all senders have been upgraded, probably years, and we'd effectively "hard fork off" all unupgraded senders once that ships.

I think it's fine if it takes years. WIth negative inbound fees and raised outbound fees, you can accomplish the exact same thing so there's no hurry. But if we eventually 'hard fork', we'll end up in a clean state. Similar to how we want to get rid of the legacy onion payload, even though there may be senders out there not supporting it.

TheBlueMatt commented 2 years ago

WIth negative inbound fees and raised outbound fees, you can accomplish the exact same thing so there's no hurry.

Not really, no. You have a similar sender-upgrade problem for raising your outbound fees. Sure, a lower one because its backwards-compatible, but you'll cause senders to avoid you until they upgrade, so even there its not really usable until a lot of senders upgrade, which takes years.

I still think this approach makes inbound fees unworkable for low-fee nodes. eg, ZFR wouldn't be able to use this despite it being uniquely useful for that type of node.

joostjager commented 2 years ago

The idea is that the negative fee provides an incentive for senders to upgrade. The negative fee and outbound raise can be tiny initially to not become unattractive compared to other routing nodes, but still enough to encourage upgrade.

And after senders upgrade, they'll run code that also deals with positive fees correctly if routing nodes at some point decide to require those.

What is the goal that you have in mind that a low/zero fee node would want to accomplish using inbound fees?

joostjager commented 2 years ago

Another important point is the probability of my prs getting merged. I think that cutting deep into the channel state machine to change the balance update logic is not something that will be cheered for unanimously. If lnd maintainers indicate otherwise, I'm happy to change my approach.

TheBlueMatt commented 2 years ago

The idea is that the negative fee provides an incentive for senders to upgrade. The negative fee and outbound raise can be tiny initially to not become unattractive compared to other routing nodes, but still enough to encourage upgrade.

I don't buy that this works in practice - imagine ZFR or some other large node wanting to charge for using a given channel, enough that people actually stop sending as much flow through it. In order to get people to back off a channel the fees applied to it need to be "material" in the sense of comparable to the average fees on the network. That would mean adding something like the average fees on the network to all their outbound channels, which is totally unacceptable unless the vast majority of senders are upgraded.

What is the goal that you have in mind that a low/zero fee node would want to accomplish using inbound fees?

Now that I write it out I think it applies to all nodes, not just "low-/zero-fee nodes", but I don't see why we should want ZFR to not be able to use inbound fees - if they have some channel that is always pushed their direction it seems like they should be willing to charge for using that channel even if they don't charge for the vast majority of their (usually-balanced) channels?

Another important point is the probability of my prs getting merged. I think that cutting deep into the channel state machine to change the balance update logic is not something that will be cheered for unanimously. If lnd maintainers indicate otherwise, I'm happy to change my approach.

This seems like a pretty terrible reason for spec-level decision, sorry. Worrying about the complexity of the patch is reasonable, and if its the cleanest way to accomplish a goal also seems totally reasonable, but I don't really buy that there's a material change in the patch size here, it just touches (slightly) different code. Either way, you're putting a new field in the channel_update and update the forwarding-fee-paid-check logic to ensure the forwarding fee was paid. There's a few extra lines in the backwards-compatible version, but its not that crazy.

More generally, if we're worried about whether something will be broadly adopted as a condition for the spec design, it definitely doesn't belong in the bLIPs repo, but rather as a BOLT change. I was really hoping the backwards-compatible version would be a reasonable thing that some nodes could support, no one else has to care, and people can start using and experimenting with to see if its helpful.

joostjager commented 2 years ago

I think it is hard to predict how routing nodes are going to experiment with the new feature. Whether they do minimal negative fees, larger negative fees, apply positive fees to some channels only, etc. That's why I think it's interesting to allow this and observe what happens.

Regarding blip vs bolt: I just wanted to document the protocol change somewhere, but it starts to feel like asking for permission.

JssDWt commented 2 years ago

Why would any sender add support for that - if no one supports it no routing node will ever turn it on.

@TheBlueMatt Senders will start supporting inbound fees because it may make payments cheaper for them at first, because routing nodes charge negative inbound fees at first. Why would a sender implement this blip only for negative inbound fees? That doesn't make sense to me. There's only a few lightning implementations, and they would implement inbound fees in both the negative and positive case for the sender, because if they wouldn't implement the positive fee case, the sender would get errors, which would hurt the UX.

TheBlueMatt commented 2 years ago

I think it is hard to predict how routing nodes are going to experiment with the new feature. Whether they do minimal negative fees, larger negative fees, apply positive fees to some channels only, etc. That's why I think it's interesting to allow this and observe what happens.

Right, I think my point is going the "negative only" approach makes it really hard to meaningfully learn what people want to use it for, and to materially experiment with it. Going the "negotiated" approach, on the other hand, allows people to experiment with it today, immediately learning how senders will respond to their fee changes without waiting for any kind of upgrade cycle and learn how well it performs in practice.

Regarding blip vs bolt: I just wanted to document the protocol change somewhere, but it starts to feel like asking for permission.

Ultimately any change that requires broad network-wide adoption does need permission - from the various implementations that need to merge that PR and get their users to deploy it. You can experiment with anything or simulate anything without bothering to write it up here or anywhere. The bLIP-vs-BOLT distinction is, in large part, that there is a huge difference between things that require broad support+upgrade and things that don't, no matter how or where its written up.

@TheBlueMatt Senders will start supporting inbound fees because it may make payments cheaper for them at first, because routing nodes charge negative inbound fees at first. Why would a sender implement this blip only for negative inbound fees? That doesn't make sense to me. There's only a few lightning implementations, and they would implement inbound fees in both the negative and positive case for the sender, because if they wouldn't implement the positive fee case, the sender would get errors, which would hurt the UX.

Oh, I'll definitely implement it to only consider the negative half of fees, because why not? Its free money for our users, definitely worth writing less code to give our users free money :)

TheBlueMatt commented 1 year ago

I went ahead and wrote up the alternative version in the above PR and implemented it in LDK at https://github.com/lightningdevkit/rust-lightning/pull/1942. I found it very trivial to implement, and requires substantially less total code changed than this proposal as I don't have to touch pathfinding. It's also, of course, more flexible in that it allows for positive inbound fees.