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

router: inbound fees support for BuildRoute #7060

Closed
13 changes: 12 additions & 1 deletion channeldb/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,13 +491,24 @@ func (c *ChannelGraph) ForEachNodeChannel(tx kvdb.RTx, node route.Vertex,
cachedInPolicy.ToNodeFeatures = toNodeFeatures
}

var inboundFee lnwire.Fee
if p1 != nil {
// Extract inbound fee. If there is a decoding error,
// skip this edge.
_, err := p1.ExtraOpaqueData.ExtractRecords(&inboundFee)
if err != nil {
return nil
}
}

directedChannel := &DirectedChannel{
ChannelID: e.ChannelID,
IsNode1: node == e.NodeKey1Bytes,
OtherNode: e.NodeKey2Bytes,
Capacity: e.Capacity,
OutPolicySet: p1 != nil,
InPolicy: cachedInPolicy,
InboundFee: inboundFee,
}

if node == e.NodeKey2Bytes {
Expand Down Expand Up @@ -3443,7 +3454,7 @@ type ChannelEdgePolicy struct {
// properly validate the set of signatures that cover these new fields,
// and ensure we're able to make upgrades to the network in a forwards
// compatible manner.
ExtraOpaqueData []byte
ExtraOpaqueData lnwire.ExtraOpaqueData

db kvdb.Backend
}
Expand Down
13 changes: 13 additions & 0 deletions channeldb/graph_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ type DirectedChannel struct {
// source, so we're always interested in the edge that arrives to us
// from the other node.
InPolicy *CachedEdgePolicy

// Inbound fees of this node.
InboundFee lnwire.Fee
}

// DeepCopy creates a deep copy of the channel, including the incoming policy.
Expand Down Expand Up @@ -304,6 +307,14 @@ func (c *GraphCache) updateOrAddEdge(node route.Vertex, edge *DirectedChannel) {
func (c *GraphCache) UpdatePolicy(policy *ChannelEdgePolicy, fromNode,
toNode route.Vertex, edge1 bool) {

// Extract inbound fee if possible and available. If there is a decoding
// error, ignore this policy.
var inboundFee lnwire.Fee
_, err := policy.ExtraOpaqueData.ExtractRecords(&inboundFee)
if err != nil {
return
}

c.mtx.Lock()
defer c.mtx.Unlock()

Expand All @@ -324,11 +335,13 @@ func (c *GraphCache) UpdatePolicy(policy *ChannelEdgePolicy, fromNode,
// policy for node 1.
case channel.IsNode1 && edge1:
channel.OutPolicySet = true
channel.InboundFee = inboundFee

// This is node 2, and it is edge 2, so this is the outgoing
// policy for node 2.
case !channel.IsNode1 && !edge1:
channel.OutPolicySet = true
channel.InboundFee = inboundFee

// The other two cases left mean it's the inbound policy for the
// node.
Expand Down
4 changes: 2 additions & 2 deletions channeldb/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -673,7 +673,7 @@ func createChannelEdge(db kvdb.Backend, node1, node2 *LightningNode) (*ChannelEd
FeeBaseMSat: 4352345,
FeeProportionalMillionths: 3452352,
Node: secondNode,
ExtraOpaqueData: []byte("new unknown feature2"),
ExtraOpaqueData: []byte{1, 0},
db: db,
}
edge2 := &ChannelEdgePolicy{
Expand All @@ -688,7 +688,7 @@ func createChannelEdge(db kvdb.Backend, node1, node2 *LightningNode) (*ChannelEd
FeeBaseMSat: 4352345,
FeeProportionalMillionths: 90392423,
Node: firstNode,
ExtraOpaqueData: []byte("new unknown feature1"),
ExtraOpaqueData: []byte{1, 0},
db: db,
}

Expand Down
21 changes: 15 additions & 6 deletions cmd/lncli/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -2019,6 +2019,12 @@ var updateChannelPolicyCommand = cli.Command{
"with a granularity of 0.000001 (millionths). Can not " +
"be set at the same time as fee_rate.",
},
cli.Int64Flag{
Name: "inbound_base_fee_msat",
},
cli.Int64Flag{
Name: "inbound_fee_rate_ppm",
},
cli.Int64Flag{
Name: "time_lock_delta",
Usage: "the CLTV delta that will be applied to all " +
Expand Down Expand Up @@ -2076,9 +2082,10 @@ func updateChannelPolicy(ctx *cli.Context) error {
defer cleanUp()

var (
baseFee int64
feeRate float64
feeRatePpm uint64
baseFee int64
feeRate float64
feeRatePpm uint64

timeLockDelta int64
err error
)
Expand Down Expand Up @@ -2150,9 +2157,11 @@ func updateChannelPolicy(ctx *cli.Context) error {
}

req := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
TimeLockDelta: uint32(timeLockDelta),
MaxHtlcMsat: ctx.Uint64("max_htlc_msat"),
BaseFeeMsat: baseFee,
TimeLockDelta: uint32(timeLockDelta),
MaxHtlcMsat: ctx.Uint64("max_htlc_msat"),
InboundBaseFeeMsat: int32(ctx.Int64("inbound_base_fee_msat")),
InboundFeeRatePpm: int32(ctx.Int64("inbound_fee_rate_ppm")),
}

if ctx.IsSet("min_htlc_msat") {
Expand Down
11 changes: 11 additions & 0 deletions docs/release-notes/release-notes-0.16.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@
wallet](https://github.com/lightningnetwork/lnd/pull/6775). NOTE that funding
PSBTs from imported tap scripts is not currently possible.

## Routing

* Experimental support for [inbound routing
fees](https://github.com/lightningnetwork/lnd/pull/6703) is added. This allows
node operators to require senders to pay an inbound fee for forwards and
payments. It is recommended to only use negative fees (an inbound "discount")
initially to keep the channels open for senders that do not recognize inbound
fees. In this release, no send support for pathfinding and route building is
added yet. We first want to learn more about the impact that inbound fees have
on the routing economy.

## Build

[The project has updated to Go
Expand Down
44 changes: 44 additions & 0 deletions htlcswitch/inbound_fee.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package htlcswitch

import "github.com/lightningnetwork/lnd/lnwire"

type InboundFee struct {
Base int32
Rate int32
}

// NewInboundFeeFromWire constructs an inbound fee structure from a wire fee.
func NewInboundFeeFromWire(fee lnwire.Fee) InboundFee {
return InboundFee{
Base: fee.BaseFee,
Rate: fee.FeeRate,
}
}

// ToWire converts the inbound fee to a wire fee structure.
func (i *InboundFee) ToWire() lnwire.Fee {
return lnwire.Fee{
BaseFee: i.Base,
FeeRate: i.Rate,
}
}

// CalcFee calculates what the inbound fee should minimally be for forwarding
// the given amount. This amount is the _outgoing_ amount, which is what the
// inbound fee is based on.
func (i *InboundFee) CalcFee(amt lnwire.MilliSatoshi) int64 {
fee := int64(i.Base)

// Calculate proportional component. Always round down in favor of the
// payer of the fee. That way rounding differences can not cause a
// payment to fail.
switch {
case i.Rate > 0:
fee += int64(i.Rate) * int64(amt) / 1e6

case i.Rate < 0:
fee += (int64(i.Rate)*int64(amt) - (1e6 - 1)) / 1e6
}

return fee
}
31 changes: 31 additions & 0 deletions htlcswitch/inbound_fee_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package htlcswitch

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestInboundFee(t *testing.T) {
// Test positive fee.
i := InboundFee{
Base: 5,
Rate: 500000,
}

require.Equal(t, int64(6), i.CalcFee(2))

// Expect fee to be rounded down.
require.Equal(t, int64(6), i.CalcFee(3))

// Test negative fee.
i = InboundFee{
Base: -5,
Rate: -500000,
}

require.Equal(t, int64(-6), i.CalcFee(2))

// Expect fee to be rounded down.
require.Equal(t, int64(-7), i.CalcFee(3))
}
1 change: 1 addition & 0 deletions htlcswitch/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ type ChannelLink interface {
CheckHtlcForward(payHash [32]byte, incomingAmt lnwire.MilliSatoshi,
amtToForward lnwire.MilliSatoshi,
incomingTimeout, outgoingTimeout uint32,
inboundFee InboundFee,
heightNow uint32, scid lnwire.ShortChannelID) *LinkError

// CheckHtlcTransit should return a nil error if the passed HTLC details
Expand Down
Loading