mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 14:22:37 +01:00
Merge pull request #6703 from bottlepay/inbound-fees
htlcswitch: add inbound routing fees receive support
This commit is contained in:
commit
a6d4bb5c89
23 changed files with 2156 additions and 1444 deletions
|
@ -115,6 +115,9 @@ type ForwardingPolicy struct {
|
|||
// used to compute the required fee for a given HTLC.
|
||||
FeeRate lnwire.MilliSatoshi
|
||||
|
||||
// InboundFee is the fee that must be paid for incoming HTLCs.
|
||||
InboundFee InboundFee
|
||||
|
||||
// TimeLockDelta is the absolute time-lock value, expressed in blocks,
|
||||
// that will be subtracted from an incoming HTLC's timelock value to
|
||||
// create the time-lock value for the forwarded outgoing HTLC. The
|
||||
|
|
|
@ -70,7 +70,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
|
||||
}
|
||||
|
||||
// Signature is a channel announcement signature, which is needed for proper
|
||||
|
|
53
channeldb/models/inbound_fee.go
Normal file
53
channeldb/models/inbound_fee.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
package models
|
||||
|
||||
import "github.com/lightningnetwork/lnd/lnwire"
|
||||
|
||||
const (
|
||||
// maxFeeRate is the maximum fee rate that we allow. It is set to allow
|
||||
// a variable fee component of up to 10x the payment amount.
|
||||
maxFeeRate = 10 * feeRateParts
|
||||
)
|
||||
|
||||
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 total of the outgoing amount plus the
|
||||
// outbound fee, which is what the inbound fee is based on.
|
||||
func (i *InboundFee) CalcFee(amt lnwire.MilliSatoshi) int64 {
|
||||
fee := int64(i.Base)
|
||||
rate := int64(i.Rate)
|
||||
|
||||
// Cap the rate to prevent overflows.
|
||||
switch {
|
||||
case rate > maxFeeRate:
|
||||
rate = maxFeeRate
|
||||
|
||||
case rate < -maxFeeRate:
|
||||
rate = -maxFeeRate
|
||||
}
|
||||
|
||||
// Calculate proportional component. To keep the integer math simple,
|
||||
// positive fees are rounded down while negative fees are rounded up.
|
||||
fee += rate * int64(amt) / feeRateParts
|
||||
|
||||
return fee
|
||||
}
|
33
channeldb/models/inbound_fee_test.go
Normal file
33
channeldb/models/inbound_fee_test.go
Normal file
|
@ -0,0 +1,33 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInboundFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// 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 up.
|
||||
require.Equal(t, int64(-6), i.CalcFee(3))
|
||||
}
|
|
@ -2166,6 +2166,31 @@ var updateChannelPolicyCommand = cli.Command{
|
|||
"0.000001 (millionths). Can not be set at " +
|
||||
"the same time as fee_rate",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "inbound_base_fee_msat",
|
||||
Usage: "the base inbound fee in milli-satoshis that " +
|
||||
"will be charged for each forwarded HTLC, " +
|
||||
"regardless of payment size. Its value must " +
|
||||
"be zero or negative - it is a discount " +
|
||||
"for using a particular incoming channel. " +
|
||||
"Note that forwards will be rejected if the " +
|
||||
"discount exceeds the outbound fee " +
|
||||
"(forward at a loss), and lead to " +
|
||||
"penalization by the sender",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "inbound_fee_rate_ppm",
|
||||
Usage: "the inbound fee rate that will be charged " +
|
||||
"proportionally based on the value of each " +
|
||||
"forwarded HTLC and the outbound fee. Fee " +
|
||||
"rate is expressed in parts per million and " +
|
||||
"must be zero or negative - it is a discount " +
|
||||
"for using a particular incoming channel." +
|
||||
"Note that forwards will be rejected if the " +
|
||||
"discount exceeds the outbound fee " +
|
||||
"(forward at a loss), and lead to " +
|
||||
"penalization by the sender",
|
||||
},
|
||||
cli.Uint64Flag{
|
||||
Name: "time_lock_delta",
|
||||
Usage: "the CLTV delta that will be applied to all " +
|
||||
|
@ -2318,10 +2343,26 @@ func updateChannelPolicy(ctx *cli.Context) error {
|
|||
}
|
||||
}
|
||||
|
||||
inboundBaseFeeMsat := ctx.Int64("inbound_base_fee_msat")
|
||||
if inboundBaseFeeMsat < math.MinInt32 ||
|
||||
inboundBaseFeeMsat > 0 {
|
||||
|
||||
return errors.New("inbound_base_fee_msat out of range")
|
||||
}
|
||||
|
||||
inboundFeeRatePpm := ctx.Int64("inbound_fee_rate_ppm")
|
||||
if inboundFeeRatePpm < math.MinInt32 ||
|
||||
inboundFeeRatePpm > 0 {
|
||||
|
||||
return errors.New("inbound_fee_rate_ppm out of range")
|
||||
}
|
||||
|
||||
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(inboundBaseFeeMsat),
|
||||
InboundFeeRatePpm: int32(inboundFeeRatePpm),
|
||||
}
|
||||
|
||||
if ctx.IsSet("min_htlc_msat") {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Release Notes
|
||||
- [Release Notes](#release-notes)
|
||||
- [Bug Fixes](#bug-fixes)
|
||||
- [New Features](#new-features)
|
||||
- [Functional Enhancements](#functional-enhancements)
|
||||
|
@ -6,12 +7,14 @@
|
|||
- [lncli Additions](#lncli-additions)
|
||||
- [Improvements](#improvements)
|
||||
- [Functional Updates](#functional-updates)
|
||||
- [Tlv](#tlv)
|
||||
- [Misc](#misc)
|
||||
- [Logging](#logging)
|
||||
- [RPC Updates](#rpc-updates)
|
||||
- [lncli Updates](#lncli-updates)
|
||||
- [Code Health](#code-health)
|
||||
- [Breaking Changes](#breaking-changes)
|
||||
- [Performance Improvements](#performance-improvements)
|
||||
- [Misc](#misc)
|
||||
- [Technical and Architectural Updates](#technical-and-architectural-updates)
|
||||
- [BOLT Spec Updates](#bolt-spec-updates)
|
||||
- [Testing](#testing)
|
||||
|
@ -109,6 +112,15 @@
|
|||
# New Features
|
||||
## Functional Enhancements
|
||||
|
||||
* 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.
|
||||
|
||||
* A new config value,
|
||||
[sweeper.maxfeerate](https://github.com/lightningnetwork/lnd/pull/7823), is
|
||||
added so users can specify the max allowed fee rate when sweeping on-chain
|
||||
|
@ -421,6 +433,7 @@ bitcoin peers' feefilter values into account](https://github.com/lightningnetwor
|
|||
* Elle Mouton
|
||||
* ErikEk
|
||||
* Jesse de Wit
|
||||
* Joost Jager
|
||||
* Keagan McClelland
|
||||
* Marcos Fernandez Perez
|
||||
* Matt Morehouse
|
||||
|
|
|
@ -247,6 +247,7 @@ type ChannelLink interface {
|
|||
CheckHtlcForward(payHash [32]byte, incomingAmt lnwire.MilliSatoshi,
|
||||
amtToForward lnwire.MilliSatoshi,
|
||||
incomingTimeout, outgoingTimeout uint32,
|
||||
inboundFee models.InboundFee,
|
||||
heightNow uint32, scid lnwire.ShortChannelID) *LinkError
|
||||
|
||||
// CheckHtlcTransit should return a nil error if the passed HTLC details
|
||||
|
|
|
@ -2780,28 +2780,43 @@ func (l *channelLink) UpdateForwardingPolicy(
|
|||
func (l *channelLink) CheckHtlcForward(payHash [32]byte,
|
||||
incomingHtlcAmt, amtToForward lnwire.MilliSatoshi,
|
||||
incomingTimeout, outgoingTimeout uint32,
|
||||
inboundFee models.InboundFee,
|
||||
heightNow uint32, originalScid lnwire.ShortChannelID) *LinkError {
|
||||
|
||||
l.RLock()
|
||||
policy := l.cfg.FwrdingPolicy
|
||||
l.RUnlock()
|
||||
|
||||
// Using the amount of the incoming HTLC, we'll calculate the expected
|
||||
// fee this incoming HTLC must carry in order to satisfy the
|
||||
// constraints of the outgoing link.
|
||||
expectedFee := ExpectedFee(policy, amtToForward)
|
||||
// Using the outgoing HTLC amount, we'll calculate the outgoing
|
||||
// fee this incoming HTLC must carry in order to satisfy the constraints
|
||||
// of the outgoing link.
|
||||
outFee := ExpectedFee(policy, amtToForward)
|
||||
|
||||
// Then calculate the inbound fee that we charge based on the sum of
|
||||
// outgoing HTLC amount and outgoing fee.
|
||||
inFee := inboundFee.CalcFee(amtToForward + outFee)
|
||||
|
||||
// Add up both fee components. It is important to calculate both fees
|
||||
// separately. An alternative way of calculating is to first determine
|
||||
// an aggregate fee and apply that to the outgoing HTLC amount. However,
|
||||
// rounding may cause the result to be slightly higher than in the case
|
||||
// of separately rounded fee components. This potentially causes failed
|
||||
// forwards for senders and is something to be avoided.
|
||||
expectedFee := inFee + int64(outFee)
|
||||
|
||||
// If the actual fee is less than our expected fee, then we'll reject
|
||||
// this HTLC as it didn't provide a sufficient amount of fees, or the
|
||||
// values have been tampered with, or the send used incorrect/dated
|
||||
// information to construct the forwarding information for this hop. In
|
||||
// any case, we'll cancel this HTLC. We're checking for this case first
|
||||
// to leak as little information as possible.
|
||||
actualFee := incomingHtlcAmt - amtToForward
|
||||
// any case, we'll cancel this HTLC.
|
||||
actualFee := int64(incomingHtlcAmt) - int64(amtToForward)
|
||||
if incomingHtlcAmt < amtToForward || actualFee < expectedFee {
|
||||
l.log.Warnf("outgoing htlc(%x) has insufficient fee: "+
|
||||
"expected %v, got %v",
|
||||
payHash[:], int64(expectedFee), int64(actualFee))
|
||||
"expected %v, got %v: incoming=%v, outgoing=%v, "+
|
||||
"inboundFee=%v",
|
||||
payHash[:], expectedFee, actualFee,
|
||||
incomingHtlcAmt, amtToForward, inboundFee,
|
||||
)
|
||||
|
||||
// As part of the returned error, we'll send our latest routing
|
||||
// policy so the sending node obtains the most up to date data.
|
||||
|
@ -3330,6 +3345,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
// round of processing.
|
||||
chanIterator.EncodeNextHop(buf)
|
||||
|
||||
inboundFee := l.cfg.FwrdingPolicy.InboundFee
|
||||
|
||||
updatePacket := &htlcPacket{
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: pd.HtlcIndex,
|
||||
|
@ -3342,6 +3359,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
incomingTimeout: pd.Timeout,
|
||||
outgoingTimeout: fwdInfo.OutgoingCTLV,
|
||||
customRecords: pld.CustomRecords(),
|
||||
inboundFee: inboundFee,
|
||||
}
|
||||
switchPackets = append(
|
||||
switchPackets, updatePacket,
|
||||
|
@ -3394,6 +3412,8 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
// have been added to switchPackets at the top of this
|
||||
// section.
|
||||
if fwdPkg.State == channeldb.FwdStateLockedIn {
|
||||
inboundFee := l.cfg.FwrdingPolicy.InboundFee
|
||||
|
||||
updatePacket := &htlcPacket{
|
||||
incomingChanID: l.ShortChanID(),
|
||||
incomingHTLCID: pd.HtlcIndex,
|
||||
|
@ -3406,6 +3426,7 @@ func (l *channelLink) processRemoteAdds(fwdPkg *channeldb.FwdPkg,
|
|||
incomingTimeout: pd.Timeout,
|
||||
outgoingTimeout: fwdInfo.OutgoingCTLV,
|
||||
customRecords: pld.CustomRecords(),
|
||||
inboundFee: inboundFee,
|
||||
}
|
||||
|
||||
fwdPkg.FwdFilter.Set(idx)
|
||||
|
|
|
@ -643,6 +643,206 @@ func testChannelLinkMultiHopPayment(t *testing.T,
|
|||
}
|
||||
}
|
||||
|
||||
func TestChannelLinkInboundFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("negative", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: -500,
|
||||
Rate: -100,
|
||||
}
|
||||
|
||||
// Bob is supposed to sent Carol 1000000 msats. For this, he
|
||||
// will charge an out fee of 1000 msat (the default hop network
|
||||
// policy). Bob's inbound fee is based on the sum of outgoing
|
||||
// htlc amount and the out fee that Bob charges. The value of
|
||||
// this sum is 1001000. The proportional component of the
|
||||
// inbound fee is -0.01% of the sum, which is -100 (rounded
|
||||
// up). Added to this is the base inbound fee of -500, making
|
||||
// for a total inbound fee of -600.
|
||||
const expectedBobInFee = -600
|
||||
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, false,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("negative overpaid", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: -500,
|
||||
Rate: -100,
|
||||
}
|
||||
|
||||
// Alice is not aware of the inbound discount and pays the full
|
||||
// outbound fee.
|
||||
const expectedBobInFee = 0
|
||||
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, false,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("negative total", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: -5000,
|
||||
}
|
||||
|
||||
const expectedBobInFee = -5000
|
||||
|
||||
// Bob's inbound discount exceeds his outbound fee. Forwards
|
||||
// carrying a negative total fee should be rejected.
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, true,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
bobInboundFee := models.InboundFee{
|
||||
Base: 1_000,
|
||||
Rate: 100_000,
|
||||
}
|
||||
|
||||
const expectedBobInFee = 101_100
|
||||
|
||||
testChannelLinkInboundFee(
|
||||
t, bobInboundFee, expectedBobInFee, false,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func testChannelLinkInboundFee(t *testing.T, //nolint:thelper
|
||||
bobInboundFee models.InboundFee, expectedBobInFee int64,
|
||||
expectedFail bool) {
|
||||
|
||||
channels, _, err := createClusterChannels(
|
||||
t, btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
|
||||
)
|
||||
require.NoError(t, err, "unable to create channel")
|
||||
|
||||
n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice,
|
||||
channels.bobToCarol, channels.carolToBob, testStartingHeight)
|
||||
|
||||
require.NoError(t, n.start())
|
||||
defer n.stop()
|
||||
|
||||
bobPolicy := n.globalPolicy
|
||||
bobPolicy.InboundFee = bobInboundFee
|
||||
n.firstBobChannelLink.UpdateForwardingPolicy(bobPolicy)
|
||||
|
||||
// Set an inbound fee for Carol. Because Carol is the payee, the fee
|
||||
// should not be applied.
|
||||
carolPolicy := n.globalPolicy
|
||||
carolPolicy.InboundFee = models.InboundFee{
|
||||
Base: -2_000,
|
||||
Rate: -200_000,
|
||||
}
|
||||
n.carolChannelLink.UpdateForwardingPolicy(carolPolicy)
|
||||
|
||||
carolBandwidthBefore := n.carolChannelLink.Bandwidth()
|
||||
firstBobBandwidthBefore := n.firstBobChannelLink.Bandwidth()
|
||||
secondBobBandwidthBefore := n.secondBobChannelLink.Bandwidth()
|
||||
aliceBandwidthBefore := n.aliceChannelLink.Bandwidth()
|
||||
|
||||
const (
|
||||
expectedCarolInboundFee = 0
|
||||
|
||||
// Expect Bob's outbound fee to match the default hop network
|
||||
// policy.
|
||||
expectedBobOutboundFee = 1_000
|
||||
)
|
||||
|
||||
amount := lnwire.MilliSatoshi(1_000_000)
|
||||
htlcAmt := lnwire.MilliSatoshi(1_000_000 +
|
||||
expectedCarolInboundFee + expectedBobOutboundFee +
|
||||
expectedBobInFee,
|
||||
)
|
||||
totalTimelock := uint32(112)
|
||||
|
||||
hops := []*hop.Payload{
|
||||
{
|
||||
FwdInfo: hop.ForwardingInfo{
|
||||
NextHop: n.carolChannelLink.
|
||||
ShortChanID(),
|
||||
AmountToForward: 1_000_000,
|
||||
OutgoingCTLV: 106,
|
||||
},
|
||||
},
|
||||
{
|
||||
FwdInfo: hop.ForwardingInfo{
|
||||
AmountToForward: 1_000_000,
|
||||
OutgoingCTLV: 106,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
receiver := n.carolServer
|
||||
firstHop := n.firstBobChannelLink.ShortChanID()
|
||||
rhash, err := makePayment(
|
||||
n.aliceServer, n.carolServer, firstHop, hops, amount, htlcAmt,
|
||||
totalTimelock,
|
||||
).Wait(30 * time.Second)
|
||||
|
||||
if expectedFail {
|
||||
require.Error(t, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err, "unable to send payment")
|
||||
|
||||
// Wait for Alice and Bob's second link to receive the revocation.
|
||||
time.Sleep(2 * time.Second)
|
||||
|
||||
// Check that Carol invoice was settled and bandwidth of HTLC
|
||||
// links were changed.
|
||||
invoice, err := receiver.registry.LookupInvoice(
|
||||
context.Background(), rhash,
|
||||
)
|
||||
require.NoError(t, err, "unable to get invoice")
|
||||
require.Equal(t, invpkg.ContractSettled, invoice.State,
|
||||
"carol invoice haven't been settled")
|
||||
|
||||
expectedAliceBandwidth := aliceBandwidthBefore - htlcAmt
|
||||
require.Equalf(t,
|
||||
expectedAliceBandwidth, n.aliceChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedAliceBandwidth, n.aliceChannelLink.Bandwidth(),
|
||||
)
|
||||
|
||||
expectedBobBandwidth1 := firstBobBandwidthBefore + htlcAmt
|
||||
require.Equalf(t,
|
||||
expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedBobBandwidth1, n.firstBobChannelLink.Bandwidth(),
|
||||
)
|
||||
|
||||
bobCarolDelta := lnwire.MilliSatoshi(
|
||||
int64(amount) + expectedCarolInboundFee,
|
||||
)
|
||||
|
||||
expectedBobBandwidth2 := secondBobBandwidthBefore - bobCarolDelta
|
||||
require.Equalf(t,
|
||||
expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedBobBandwidth2, n.secondBobChannelLink.Bandwidth(),
|
||||
)
|
||||
|
||||
expectedCarolBandwidth := carolBandwidthBefore + bobCarolDelta
|
||||
require.Equalf(t,
|
||||
expectedCarolBandwidth, n.carolChannelLink.Bandwidth(),
|
||||
"channel bandwidth incorrect: expected %v, got %v",
|
||||
expectedCarolBandwidth, n.carolChannelLink.Bandwidth(),
|
||||
)
|
||||
}
|
||||
|
||||
// TestChannelLinkCancelFullCommitment tests the ability for links to cancel
|
||||
// forwarded HTLCs once all of their commitment slots are full.
|
||||
func TestChannelLinkCancelFullCommitment(t *testing.T) {
|
||||
|
@ -5994,7 +6194,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
t.Run("satisfied", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if result != nil {
|
||||
t.Fatalf("expected policy to be satisfied")
|
||||
}
|
||||
|
@ -6002,7 +6204,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
t.Run("below minhtlc", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 100, 50,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailAmountBelowMinimum); !ok {
|
||||
t.Fatalf("expected FailAmountBelowMinimum failure code")
|
||||
}
|
||||
|
@ -6010,7 +6214,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
t.Run("above maxhtlc", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1200,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailTemporaryChannelFailure); !ok {
|
||||
t.Fatalf("expected FailTemporaryChannelFailure failure code")
|
||||
}
|
||||
|
@ -6018,7 +6224,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
t.Run("insufficient fee", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1005, 1000,
|
||||
200, 150, 0, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailFeeInsufficient); !ok {
|
||||
t.Fatalf("expected FailFeeInsufficient failure code")
|
||||
}
|
||||
|
@ -6031,7 +6239,7 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
result := link.CheckHtlcForward(
|
||||
hash, 100005, 100000, 200,
|
||||
150, 0, lnwire.ShortChannelID{},
|
||||
150, models.InboundFee{}, 0, lnwire.ShortChannelID{},
|
||||
)
|
||||
_, ok := result.WireMessage().(*lnwire.FailFeeInsufficient)
|
||||
require.True(t, ok, "expected FailFeeInsufficient failure code")
|
||||
|
@ -6039,7 +6247,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
t.Run("expiry too soon", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 150, 190, lnwire.ShortChannelID{})
|
||||
200, 150, models.InboundFee{}, 190,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailExpiryTooSoon); !ok {
|
||||
t.Fatalf("expected FailExpiryTooSoon failure code")
|
||||
}
|
||||
|
@ -6047,7 +6257,9 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
|
||||
t.Run("incorrect cltv expiry", func(t *testing.T) {
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
200, 190, 0, lnwire.ShortChannelID{})
|
||||
200, 190, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailIncorrectCltvExpiry); !ok {
|
||||
t.Fatalf("expected FailIncorrectCltvExpiry failure code")
|
||||
}
|
||||
|
@ -6057,11 +6269,37 @@ func TestCheckHtlcForward(t *testing.T) {
|
|||
t.Run("cltv expiry too far in the future", func(t *testing.T) {
|
||||
// Check that expiry isn't too far in the future.
|
||||
result := link.CheckHtlcForward(hash, 1500, 1000,
|
||||
10200, 10100, 0, lnwire.ShortChannelID{})
|
||||
10200, 10100, models.InboundFee{}, 0,
|
||||
lnwire.ShortChannelID{},
|
||||
)
|
||||
if _, ok := result.WireMessage().(*lnwire.FailExpiryTooFar); !ok {
|
||||
t.Fatalf("expected FailExpiryTooFar failure code")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inbound fee satisfied", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := link.CheckHtlcForward(hash, 1000+10-2-1, 1000,
|
||||
200, 150, models.InboundFee{Base: -2, Rate: -1_000},
|
||||
0, lnwire.ShortChannelID{})
|
||||
if result != nil {
|
||||
t.Fatalf("expected policy to be satisfied")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("inbound fee insufficient", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := link.CheckHtlcForward(hash, 1000+10-10-101-1, 1000,
|
||||
200, 150, models.InboundFee{Base: -10, Rate: -100_000},
|
||||
0, lnwire.ShortChannelID{})
|
||||
|
||||
msg := result.WireMessage()
|
||||
if _, ok := msg.(*lnwire.FailFeeInsufficient); !ok {
|
||||
t.Fatalf("expected FailFeeInsufficient failure code")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestChannelLinkCanceledInvoice in this test checks the interaction
|
||||
|
|
|
@ -834,7 +834,7 @@ func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
|
|||
func (f *mockChannelLink) UpdateForwardingPolicy(_ models.ForwardingPolicy) {
|
||||
}
|
||||
func (f *mockChannelLink) CheckHtlcForward([32]byte, lnwire.MilliSatoshi,
|
||||
lnwire.MilliSatoshi, uint32, uint32, uint32,
|
||||
lnwire.MilliSatoshi, uint32, uint32, models.InboundFee, uint32,
|
||||
lnwire.ShortChannelID) *LinkError {
|
||||
|
||||
return f.checkHtlcForwardResult
|
||||
|
|
|
@ -2,6 +2,7 @@ package htlcswitch
|
|||
|
||||
import (
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
|
@ -103,6 +104,9 @@ type htlcPacket struct {
|
|||
// but receives a channel_update with the alias SCID. Instead, the
|
||||
// payer should receive a channel_update with the public SCID.
|
||||
originalOutgoingChanID lnwire.ShortChannelID
|
||||
|
||||
// inboundFee is the fee schedule of the incoming channel.
|
||||
inboundFee models.InboundFee
|
||||
}
|
||||
|
||||
// inKey returns the circuit key used to identify the incoming htlc.
|
||||
|
|
|
@ -1178,7 +1178,9 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
|||
failure = link.CheckHtlcForward(
|
||||
htlc.PaymentHash, packet.incomingAmount,
|
||||
packet.amount, packet.incomingTimeout,
|
||||
packet.outgoingTimeout, currentHeight,
|
||||
packet.outgoingTimeout,
|
||||
packet.inboundFee,
|
||||
currentHeight,
|
||||
packet.originalOutgoingChanID,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -74,14 +74,29 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
|
|||
const aliceFeeRatePPM = 100000
|
||||
updateChannelPolicy(
|
||||
ht, alice, chanPointAlice, aliceBaseFeeSat*1000,
|
||||
aliceFeeRatePPM, chainreg.DefaultBitcoinTimeLockDelta,
|
||||
aliceFeeRatePPM, 0, 0, chainreg.DefaultBitcoinTimeLockDelta,
|
||||
maxHtlc, carol,
|
||||
)
|
||||
|
||||
// Define a negative inbound fee for Alice, to verify that this is
|
||||
// backwards compatible with an older sender ignoring the discount.
|
||||
const (
|
||||
aliceInboundBaseFeeMsat = -1
|
||||
aliceInboundFeeRate = -10000
|
||||
)
|
||||
|
||||
updateChannelPolicy(
|
||||
ht, alice, chanPointDave, 0, 0,
|
||||
aliceInboundBaseFeeMsat, aliceInboundFeeRate,
|
||||
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc,
|
||||
dave,
|
||||
)
|
||||
|
||||
const daveBaseFeeSat = 5
|
||||
const daveFeeRatePPM = 150000
|
||||
updateChannelPolicy(
|
||||
ht, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM,
|
||||
0, 0,
|
||||
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, carol,
|
||||
)
|
||||
|
||||
|
@ -104,8 +119,9 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
|
|||
ht.AssertAmountPaid("Alice(local) => Bob(remote)", alice,
|
||||
chanPointAlice, expectedAmountPaidAtoB, int64(0))
|
||||
|
||||
// To forward a payment of 1000 sat, Alice is charging a fee of
|
||||
// 1 sat + 10% = 101 sat.
|
||||
// To forward a payment of 1000 sat, Alice is charging a fee of 1 sat +
|
||||
// 10% = 101 sat. Note that this does not include the inbound fee
|
||||
// (discount) because there is no sender support yet.
|
||||
const aliceFeePerPayment = aliceBaseFeeSat +
|
||||
(paymentAmt * aliceFeeRatePPM / 1_000_000)
|
||||
const expectedFeeAlice = numPayments * aliceFeePerPayment
|
||||
|
@ -224,15 +240,17 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
|
|||
// NOTE: only used in current test.
|
||||
func updateChannelPolicy(ht *lntest.HarnessTest, hn *node.HarnessNode,
|
||||
chanPoint *lnrpc.ChannelPoint, baseFee int64,
|
||||
feeRate int64, timeLockDelta uint32,
|
||||
maxHtlc uint64, listenerNode *node.HarnessNode) {
|
||||
feeRate int64, inboundBaseFee, inboundFeeRate int32,
|
||||
timeLockDelta uint32, maxHtlc uint64, listenerNode *node.HarnessNode) {
|
||||
|
||||
expectedPolicy := &lnrpc.RoutingPolicy{
|
||||
FeeBaseMsat: baseFee,
|
||||
FeeRateMilliMsat: feeRate,
|
||||
TimeLockDelta: timeLockDelta,
|
||||
MinHtlc: 1000, // default value
|
||||
MaxHtlcMsat: maxHtlc,
|
||||
FeeBaseMsat: baseFee,
|
||||
FeeRateMilliMsat: feeRate,
|
||||
TimeLockDelta: timeLockDelta,
|
||||
MinHtlc: 1000, // default value
|
||||
MaxHtlcMsat: maxHtlc,
|
||||
InboundFeeBaseMsat: inboundBaseFee,
|
||||
InboundFeeRateMilliMsat: inboundFeeRate,
|
||||
}
|
||||
|
||||
updateFeeReq := &lnrpc.PolicyUpdateRequest{
|
||||
|
@ -242,7 +260,9 @@ func updateChannelPolicy(ht *lntest.HarnessTest, hn *node.HarnessNode,
|
|||
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
|
||||
ChanPoint: chanPoint,
|
||||
},
|
||||
MaxHtlcMsat: maxHtlc,
|
||||
MaxHtlcMsat: maxHtlc,
|
||||
InboundBaseFeeMsat: inboundBaseFee,
|
||||
InboundFeeRatePpm: inboundFeeRate,
|
||||
}
|
||||
|
||||
hn.RPC.UpdateChannelPolicy(updateFeeReq)
|
||||
|
|
|
@ -217,6 +217,14 @@
|
|||
"format": "byte"
|
||||
},
|
||||
"description": "Custom channel update tlv records."
|
||||
},
|
||||
"inbound_fee_base_msat": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"inbound_fee_rate_milli_msat": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -3330,6 +3330,9 @@ message RoutingPolicy {
|
|||
|
||||
// Custom channel update tlv records.
|
||||
map<uint64, bytes> custom_records = 8;
|
||||
|
||||
int32 inbound_fee_base_msat = 9;
|
||||
int32 inbound_fee_rate_milli_msat = 10;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -4309,7 +4312,15 @@ message ChannelFeeReport {
|
|||
// The effective fee rate in milli-satoshis. Computed by dividing the
|
||||
// fee_per_mil value by 1 million.
|
||||
double fee_rate = 4;
|
||||
|
||||
// The base fee charged regardless of the number of milli-satoshis sent.
|
||||
int32 inbound_base_fee_msat = 6;
|
||||
|
||||
// The amount charged per milli-satoshis transferred expressed in
|
||||
// millionths of a satoshi.
|
||||
int32 inbound_fee_per_mil = 7;
|
||||
}
|
||||
|
||||
message FeeReportResponse {
|
||||
// An array of channel fee reports which describes the current fee schedule
|
||||
// for each channel.
|
||||
|
@ -4360,7 +4371,11 @@ message PolicyUpdateRequest {
|
|||
|
||||
// If true, min_htlc_msat is applied.
|
||||
bool min_htlc_msat_specified = 8;
|
||||
|
||||
int32 inbound_base_fee_msat = 10;
|
||||
int32 inbound_fee_rate_ppm = 11;
|
||||
}
|
||||
|
||||
enum UpdateFailure {
|
||||
UPDATE_FAILURE_UNKNOWN = 0;
|
||||
UPDATE_FAILURE_PENDING = 1;
|
||||
|
|
|
@ -4238,6 +4238,16 @@
|
|||
"type": "number",
|
||||
"format": "double",
|
||||
"description": "The effective fee rate in milli-satoshis. Computed by dividing the\nfee_per_mil value by 1 million."
|
||||
},
|
||||
"inbound_base_fee_msat": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The base fee charged regardless of the number of milli-satoshis sent."
|
||||
},
|
||||
"inbound_fee_per_mil": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"description": "The amount charged per milli-satoshis transferred expressed in\nmillionths of a satoshi."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -6536,6 +6546,14 @@
|
|||
"min_htlc_msat_specified": {
|
||||
"type": "boolean",
|
||||
"description": "If true, min_htlc_msat is applied."
|
||||
},
|
||||
"inbound_base_fee_msat": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"inbound_fee_rate_ppm": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -6840,6 +6858,14 @@
|
|||
"format": "byte"
|
||||
},
|
||||
"description": "Custom channel update tlv records."
|
||||
},
|
||||
"inbound_fee_base_msat": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
},
|
||||
"inbound_fee_rate_milli_msat": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
60
lnwire/typed_fee.go
Normal file
60
lnwire/typed_fee.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package lnwire
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/lightningnetwork/lnd/tlv"
|
||||
)
|
||||
|
||||
const (
|
||||
FeeRecordType tlv.Type = 55555
|
||||
)
|
||||
|
||||
// Fee represents a fee schedule.
|
||||
type Fee struct {
|
||||
BaseFee int32
|
||||
FeeRate int32
|
||||
}
|
||||
|
||||
// Record returns a TLV record that can be used to encode/decode the fee
|
||||
// type from a given TLV stream.
|
||||
func (l *Fee) Record() tlv.Record {
|
||||
return tlv.MakeStaticRecord(
|
||||
FeeRecordType, l, 8, feeEncoder, feeDecoder, //nolint:gomnd
|
||||
)
|
||||
}
|
||||
|
||||
// feeEncoder is a custom TLV encoder for the fee record.
|
||||
func feeEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
|
||||
v, ok := val.(*Fee)
|
||||
if !ok {
|
||||
return tlv.NewTypeForEncodingErr(val, "lnwire.Fee")
|
||||
}
|
||||
|
||||
if err := tlv.EUint32T(w, uint32(v.BaseFee), buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tlv.EUint32T(w, uint32(v.FeeRate), buf)
|
||||
}
|
||||
|
||||
// feeDecoder is a custom TLV decoder for the fee record.
|
||||
func feeDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
|
||||
v, ok := val.(*Fee)
|
||||
if !ok {
|
||||
return tlv.NewTypeForDecodingErr(val, "lnwire.Fee", l, 8)
|
||||
}
|
||||
|
||||
var baseFee, feeRate uint32
|
||||
if err := tlv.DUint32(r, &baseFee, buf, 4); err != nil { //nolint: gomnd,lll
|
||||
return err
|
||||
}
|
||||
if err := tlv.DUint32(r, &feeRate, buf, 4); err != nil { //nolint: gomnd,lll
|
||||
return err
|
||||
}
|
||||
|
||||
v.FeeRate = int32(feeRate)
|
||||
v.BaseFee = int32(baseFee)
|
||||
|
||||
return nil
|
||||
}
|
40
lnwire/typed_fee_test.go
Normal file
40
lnwire/typed_fee_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package lnwire
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTypedFee(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("positive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testTypedFee(t, Fee{
|
||||
BaseFee: 10,
|
||||
FeeRate: 20,
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("negative", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testTypedFee(t, Fee{
|
||||
BaseFee: -10,
|
||||
FeeRate: -20,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testTypedFee(t *testing.T, fee Fee) { //nolint: thelper
|
||||
var eob ExtraOpaqueData
|
||||
require.NoError(t, eob.PackRecords(&fee))
|
||||
|
||||
var extractedFee Fee
|
||||
_, err := eob.ExtractRecords(&extractedFee)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, fee, extractedFee)
|
||||
}
|
|
@ -961,12 +961,26 @@ func (p *Brontide) loadActiveChannels(chans []*channeldb.OpenChannel) (
|
|||
// routing policy into a forwarding policy.
|
||||
var forwardingPolicy *models.ForwardingPolicy
|
||||
if selfPolicy != nil {
|
||||
var inboundWireFee lnwire.Fee
|
||||
_, err := selfPolicy.ExtraOpaqueData.ExtractRecords(
|
||||
&inboundWireFee,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inboundFee := models.NewInboundFeeFromWire(
|
||||
inboundWireFee,
|
||||
)
|
||||
|
||||
forwardingPolicy = &models.ForwardingPolicy{
|
||||
MinHTLCOut: selfPolicy.MinHTLC,
|
||||
MaxHTLC: selfPolicy.MaxHTLC,
|
||||
BaseFee: selfPolicy.FeeBaseMSat,
|
||||
FeeRate: selfPolicy.FeeProportionalMillionths,
|
||||
TimeLockDelta: uint32(selfPolicy.TimeLockDelta),
|
||||
|
||||
InboundFee: inboundFee,
|
||||
}
|
||||
} else {
|
||||
p.log.Warnf("Unable to find our forwarding policy "+
|
||||
|
|
|
@ -112,6 +112,7 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
|
|||
TimeLockDelta: uint32(edge.TimeLockDelta),
|
||||
MinHTLCOut: edge.MinHTLC,
|
||||
MaxHTLC: edge.MaxHTLC,
|
||||
InboundFee: newSchema.InboundFee,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -180,6 +181,12 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint,
|
|||
edge.FeeProportionalMillionths = lnwire.MilliSatoshi(
|
||||
newSchema.FeeRate,
|
||||
)
|
||||
|
||||
inboundFee := newSchema.InboundFee.ToWire()
|
||||
if err := edge.ExtraOpaqueData.PackRecords(&inboundFee); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
edge.TimeLockDelta = uint16(newSchema.TimeLockDelta)
|
||||
|
||||
// Retrieve negotiated channel htlc amt limits.
|
||||
|
|
|
@ -287,6 +287,10 @@ type FeeSchema struct {
|
|||
// the effective fee rate charged per mSAT will be: (amount *
|
||||
// FeeRate/1,000,000).
|
||||
FeeRate uint32
|
||||
|
||||
// InboundFee is the inbound fee schedule that applies to forwards
|
||||
// coming in through a channel to which this FeeSchema pertains.
|
||||
InboundFee models.InboundFee
|
||||
}
|
||||
|
||||
// ChannelPolicy holds the parameters that determine the policy we enforce
|
||||
|
@ -2763,6 +2767,7 @@ func (r *ChannelRouter) applyChannelUpdate(msg *lnwire.ChannelUpdate) bool {
|
|||
MaxHTLC: msg.HtlcMaximumMsat,
|
||||
FeeBaseMSat: lnwire.MilliSatoshi(msg.BaseFee),
|
||||
FeeProportionalMillionths: lnwire.MilliSatoshi(msg.FeeRate),
|
||||
ExtraOpaqueData: msg.ExtraOpaqueData,
|
||||
})
|
||||
if err != nil && !IsError(err, ErrIgnored, ErrOutdated) {
|
||||
log.Errorf("Unable to apply channel update: %v", err)
|
||||
|
|
37
rpcserver.go
37
rpcserver.go
|
@ -6059,6 +6059,23 @@ func marshalExtraOpaqueData(data []byte) map[uint64][]byte {
|
|||
return records
|
||||
}
|
||||
|
||||
// extractInboundFeeSafe tries to extract the inbound fee from the given extra
|
||||
// opaque data tlv block. If parsing fails, a zero inbound fee is returned. This
|
||||
// function is typically used on unvalidated data coming stored in the database.
|
||||
// There is not much we can do other than ignoring errors here.
|
||||
func extractInboundFeeSafe(data lnwire.ExtraOpaqueData) lnwire.Fee {
|
||||
var inboundFee lnwire.Fee
|
||||
|
||||
_, err := data.ExtractRecords(&inboundFee)
|
||||
if err != nil {
|
||||
// Return zero fee. Do not return the inboundFee variable
|
||||
// because it may be undefined.
|
||||
return lnwire.Fee{}
|
||||
}
|
||||
|
||||
return inboundFee
|
||||
}
|
||||
|
||||
func marshalDBEdge(edgeInfo *models.ChannelEdgeInfo,
|
||||
c1, c2 *models.ChannelEdgePolicy) *lnrpc.ChannelEdge {
|
||||
|
||||
|
@ -6108,6 +6125,7 @@ func marshalDBRoutingPolicy(
|
|||
disabled := policy.ChannelFlags&lnwire.ChanUpdateDisabled != 0
|
||||
|
||||
customRecords := marshalExtraOpaqueData(policy.ExtraOpaqueData)
|
||||
inboundFee := extractInboundFeeSafe(policy.ExtraOpaqueData)
|
||||
|
||||
return &lnrpc.RoutingPolicy{
|
||||
TimeLockDelta: uint32(policy.TimeLockDelta),
|
||||
|
@ -6118,6 +6136,9 @@ func marshalDBRoutingPolicy(
|
|||
Disabled: disabled,
|
||||
LastUpdate: uint32(policy.LastUpdate.Unix()),
|
||||
CustomRecords: customRecords,
|
||||
|
||||
InboundFeeBaseMsat: inboundFee.BaseFee,
|
||||
InboundFeeRateMilliMsat: inboundFee.FeeRate,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6856,6 +6877,15 @@ func (r *rpcServer) FeeReport(ctx context.Context,
|
|||
edgePolicy.FeeProportionalMillionths
|
||||
feeRate := float64(feeRateFixedPoint) / feeBase
|
||||
|
||||
// Decode inbound fee from extra data.
|
||||
var inboundFee lnwire.Fee
|
||||
_, err := edgePolicy.ExtraOpaqueData.ExtractRecords(
|
||||
&inboundFee,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(roasbeef): also add stats for revenue for each
|
||||
// channel
|
||||
feeReports = append(feeReports, &lnrpc.ChannelFeeReport{
|
||||
|
@ -6864,6 +6894,9 @@ func (r *rpcServer) FeeReport(ctx context.Context,
|
|||
BaseFeeMsat: int64(edgePolicy.FeeBaseMSat),
|
||||
FeePerMil: int64(feeRateFixedPoint),
|
||||
FeeRate: feeRate,
|
||||
|
||||
InboundBaseFeeMsat: inboundFee.BaseFee,
|
||||
InboundFeePerMil: inboundFee.FeeRate,
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -7050,6 +7083,10 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
|
|||
feeSchema := routing.FeeSchema{
|
||||
BaseFee: baseFeeMsat,
|
||||
FeeRate: feeRateFixed,
|
||||
InboundFee: models.InboundFee{
|
||||
Base: req.InboundBaseFeeMsat,
|
||||
Rate: req.InboundFeeRatePpm,
|
||||
},
|
||||
}
|
||||
|
||||
maxHtlc := lnwire.MilliSatoshi(req.MaxHtlcMsat)
|
||||
|
|
Loading…
Add table
Reference in a new issue