Merge pull request #8758 from feelancer21/preserve-inbound-fees

multi: Inbound fees are retained when not provided
This commit is contained in:
Olaoluwa Osuntokun 2024-05-23 13:58:52 -07:00 committed by GitHub
commit bc6292f8bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1508 additions and 1333 deletions

View File

@ -2387,12 +2387,28 @@ func updateChannelPolicy(ctx *cli.Context) error {
return errors.New("inbound_fee_rate_ppm out of range")
}
// Inbound fees are optional. However, if an update is required,
// both the base fee and the fee rate must be provided.
var inboundFee *lnrpc.InboundFee
if ctx.IsSet("inbound_base_fee_msat") !=
ctx.IsSet("inbound_fee_rate_ppm") {
return errors.New("both parameters must be provided: " +
"inbound_base_fee_msat and inbound_fee_rate_ppm")
}
if ctx.IsSet("inbound_fee_rate_ppm") {
inboundFee = &lnrpc.InboundFee{
BaseFeeMsat: int32(inboundBaseFeeMsat),
FeeRatePpm: int32(inboundFeeRatePpm),
}
}
req := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
TimeLockDelta: uint32(timeLockDelta),
MaxHtlcMsat: ctx.Uint64("max_htlc_msat"),
InboundBaseFeeMsat: int32(inboundBaseFeeMsat),
InboundFeeRatePpm: int32(inboundFeeRatePpm),
BaseFeeMsat: baseFee,
TimeLockDelta: uint32(timeLockDelta),
MaxHtlcMsat: ctx.Uint64("max_htlc_msat"),
InboundFee: inboundFee,
}
if ctx.IsSet("min_htlc_msat") {

View File

@ -45,7 +45,9 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
nodes := []*node.HarnessNode{alice, bob}
// Alice and Bob should see each other's ChannelUpdates, advertising the
// default routing policies.
// default routing policies. We do not currently set any inbound fees.
// The inbound base and inbound fee rate are advertised with a default
// of 0.
expectedPolicy := &lnrpc.RoutingPolicy{
FeeBaseMsat: defaultFeeBase,
FeeRateMilliMsat: defaultFeeRate,
@ -227,9 +229,9 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
require.NoError(ht, err, "unable to receive payment stream")
require.Empty(ht, sendResp.PaymentError, "expected payment to succeed")
// With our little cluster set up, we'll update the fees and the max
// htlc size for the Bob side of the Alice->Bob channel, and make sure
// all nodes learn about it.
// With our little cluster set up, we'll update the outbound fees and
// the max htlc size for the Bob side of the Alice->Bob channel, and
// make sure all nodes learn about it. Inbound fees remain at 0.
baseFee := int64(1500)
feeRate := int64(12)
timeLockDelta := uint32(66)
@ -298,17 +300,25 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
feeRate = int64(123)
timeLockDelta = uint32(22)
maxHtlc *= 2
inboundBaseFee := int32(-400)
inboundFeeRatePpm := int32(-60)
expectedPolicy.FeeBaseMsat = baseFee
expectedPolicy.FeeRateMilliMsat = testFeeBase * feeRate
expectedPolicy.TimeLockDelta = timeLockDelta
expectedPolicy.MaxHtlcMsat = maxHtlc
expectedPolicy.InboundFeeBaseMsat = inboundBaseFee
expectedPolicy.InboundFeeRateMilliMsat = inboundFeeRatePpm
req = &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate),
TimeLockDelta: timeLockDelta,
MaxHtlcMsat: maxHtlc,
InboundFee: &lnrpc.InboundFee{
BaseFeeMsat: inboundBaseFee,
FeeRatePpm: inboundFeeRatePpm,
},
}
req.Scope = &lnrpc.PolicyUpdateRequest_Global{}
alice.RPC.UpdateChannelPolicy(req)
@ -370,10 +380,13 @@ func testUpdateChannelPolicy(ht *lntest.HarnessTest) {
)
}
// Double the base fee and attach to the policy.
// Double the base fee and attach to the policy. Moreover, we set the
// inbound fee to nil and test that it does not change the propagated
// inbound fee.
baseFee1 := baseFee * 2
expectedPolicy.FeeBaseMsat = baseFee1
req.BaseFeeMsat = baseFee1
req.InboundFee = nil
assertAliceAndBob(req, expectedPolicy)
// Check that Carol has both heard the policy and updated it in her

View File

@ -74,8 +74,8 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
const aliceFeeRatePPM = 100000
updateChannelPolicy(
ht, alice, chanPointAlice, aliceBaseFeeSat*1000,
aliceFeeRatePPM, 0, 0, chainreg.DefaultBitcoinTimeLockDelta,
maxHtlc, carol,
aliceFeeRatePPM, 0, 0, false,
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, carol,
)
// Define a negative inbound fee for Alice, to verify that this is
@ -85,9 +85,19 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
aliceInboundFeeRate = -50000 // 5%
)
// We update the channel twice. The first time we set the inbound fee,
// the second time we don't. This is done to test whether the switch is
// still aware of the inbound fees.
updateChannelPolicy(
ht, alice, chanPointDave, 0, 0,
aliceInboundBaseFeeMsat, aliceInboundFeeRate,
aliceInboundBaseFeeMsat, aliceInboundFeeRate, true,
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc,
dave,
)
updateChannelPolicy(
ht, alice, chanPointDave, 0, 0,
aliceInboundBaseFeeMsat, aliceInboundFeeRate, false,
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc,
dave,
)
@ -96,7 +106,7 @@ func testMultiHopPayments(ht *lntest.HarnessTest) {
const daveFeeRatePPM = 150000
updateChannelPolicy(
ht, dave, chanPointDave, daveBaseFeeSat*1000, daveFeeRatePPM,
0, 0,
0, 0, true,
chainreg.DefaultBitcoinTimeLockDelta, maxHtlc, carol,
)
@ -236,8 +246,8 @@ 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, inboundBaseFee, inboundFeeRate int32,
chanPoint *lnrpc.ChannelPoint, baseFee int64, feeRate int64,
inboundBaseFee, inboundFeeRate int32, updateInboundFee bool,
timeLockDelta uint32, maxHtlc uint64, listenerNode *node.HarnessNode) {
expectedPolicy := &lnrpc.RoutingPolicy{
@ -250,6 +260,14 @@ func updateChannelPolicy(ht *lntest.HarnessTest, hn *node.HarnessNode,
InboundFeeRateMilliMsat: inboundFeeRate,
}
var inboundFee *lnrpc.InboundFee
if updateInboundFee {
inboundFee = &lnrpc.InboundFee{
BaseFeeMsat: inboundBaseFee,
FeeRatePpm: inboundFeeRate,
}
}
updateFeeReq := &lnrpc.PolicyUpdateRequest{
BaseFeeMsat: baseFee,
FeeRate: float64(feeRate) / testFeeBase,
@ -257,9 +275,8 @@ func updateChannelPolicy(ht *lntest.HarnessTest, hn *node.HarnessNode,
Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{
ChanPoint: chanPoint,
},
MaxHtlcMsat: maxHtlc,
InboundBaseFeeMsat: inboundBaseFee,
InboundFeeRatePpm: inboundFeeRate,
MaxHtlcMsat: maxHtlc,
InboundFee: inboundFee,
}
hn.RPC.UpdateChannelPolicy(updateFeeReq)

File diff suppressed because it is too large Load Diff

View File

@ -4369,6 +4369,16 @@ message FeeReportResponse {
uint64 month_fee_sum = 4;
}
message InboundFee {
// The inbound base fee charged regardless of the number of milli-satoshis
// received in the channel. By default, only negative values are accepted.
int32 base_fee_msat = 1;
// The effective inbound fee rate in micro-satoshis (parts per million).
// By default, only negative values are accepted.
int32 fee_rate_ppm = 2;
}
message PolicyUpdateRequest {
oneof scope {
// If set, then this update applies to all currently active channels.
@ -4402,8 +4412,9 @@ 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;
// Optional inbound fee. If unset, the previously set value will be
// retained [EXPERIMENTAL].
InboundFee inbound_fee = 10;
}
enum UpdateFailure {

View File

@ -5297,6 +5297,21 @@
}
}
},
"lnrpcInboundFee": {
"type": "object",
"properties": {
"base_fee_msat": {
"type": "integer",
"format": "int32",
"description": "The inbound base fee charged regardless of the number of milli-satoshis\nreceived in the channel. By default, only negative values are accepted."
},
"fee_rate_ppm": {
"type": "integer",
"format": "int32",
"description": "The effective inbound fee rate in micro-satoshis (parts per million).\nBy default, only negative values are accepted."
}
}
},
"lnrpcInitiator": {
"type": "string",
"enum": [
@ -6585,13 +6600,9 @@
"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"
"inbound_fee": {
"$ref": "#/definitions/lnrpcInboundFee",
"description": "Optional inbound fee. If unset, the previously set value will be\nretained [EXPERIMENTAL]."
}
}
},

View File

@ -681,6 +681,18 @@ func CheckChannelPolicy(policy, expectedPolicy *lnrpc.RoutingPolicy) error {
return fmt.Errorf("expected max htlc %v, got %v",
expectedPolicy.MaxHtlcMsat, policy.MaxHtlcMsat)
}
if policy.InboundFeeBaseMsat != expectedPolicy.InboundFeeBaseMsat {
return fmt.Errorf("expected inbound base fee %v, got %v",
expectedPolicy.InboundFeeBaseMsat,
policy.InboundFeeBaseMsat)
}
if policy.InboundFeeRateMilliMsat !=
expectedPolicy.InboundFeeRateMilliMsat {
return fmt.Errorf("expected inbound fee rate %v, got %v",
expectedPolicy.InboundFeeRateMilliMsat,
policy.InboundFeeRateMilliMsat)
}
if policy.Disabled != expectedPolicy.Disabled {
return errors.New("edge should be disabled but isn't")
}

View File

@ -9,6 +9,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/kvdb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
@ -105,6 +106,14 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
Edge: edge,
})
// Extract inbound fees from the ExtraOpaqueData.
var inboundWireFee lnwire.Fee
_, err = edge.ExtraOpaqueData.ExtractRecords(&inboundWireFee)
if err != nil {
return err
}
inboundFee := models.NewInboundFeeFromWire(inboundWireFee)
// Add updated policy to list of policies to send to switch.
policiesToUpdate[info.ChannelPoint] = models.ForwardingPolicy{
BaseFee: edge.FeeBaseMSat,
@ -112,7 +121,7 @@ func (r *Manager) UpdatePolicy(newSchema routing.ChannelPolicy,
TimeLockDelta: uint32(edge.TimeLockDelta),
MinHTLCOut: edge.MinHTLC,
MaxHTLC: edge.MaxHTLC,
InboundFee: newSchema.InboundFee,
InboundFee: inboundFee,
}
return nil
@ -182,8 +191,15 @@ func (r *Manager) updateEdge(tx kvdb.RTx, chanPoint wire.OutPoint,
newSchema.FeeRate,
)
inboundFee := newSchema.InboundFee.ToWire()
if err := edge.ExtraOpaqueData.PackRecords(&inboundFee); err != nil {
// If inbound fees are set, we update the edge with them.
err := fn.MapOptionZ(newSchema.InboundFee,
func(f models.InboundFee) error {
inboundWireFee := f.ToWire()
return edge.ExtraOpaqueData.PackRecords(
&inboundWireFee,
)
})
if err != nil {
return err
}

View File

@ -22,6 +22,7 @@ import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/channeldb/models"
"github.com/lightningnetwork/lnd/clock"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/kvdb"
@ -290,7 +291,7 @@ type FeeSchema struct {
// InboundFee is the inbound fee schedule that applies to forwards
// coming in through a channel to which this FeeSchema pertains.
InboundFee models.InboundFee
InboundFee fn.Option[models.InboundFee]
}
// ChannelPolicy holds the parameters that determine the policy we enforce

View File

@ -46,6 +46,7 @@ import (
"github.com/lightningnetwork/lnd/contractcourt"
"github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
@ -7216,27 +7217,35 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
}
// By default, positive inbound fees are rejected.
if !r.cfg.AcceptPositiveInboundFees {
if req.InboundBaseFeeMsat > 0 {
if !r.cfg.AcceptPositiveInboundFees && req.InboundFee != nil {
if req.InboundFee.BaseFeeMsat > 0 {
return nil, fmt.Errorf("positive values for inbound "+
"base fee msat are not supported: %v",
req.InboundBaseFeeMsat)
req.InboundFee.BaseFeeMsat)
}
if req.InboundFeeRatePpm > 0 {
if req.InboundFee.FeeRatePpm > 0 {
return nil, fmt.Errorf("positive values for inbound "+
"fee rate ppm are not supported: %v",
req.InboundFeeRatePpm)
req.InboundFee.FeeRatePpm)
}
}
// If no inbound fees have been specified, we indicate with an empty
// option that the previous inbound fee should be retained during the
// edge update.
inboundFee := fn.None[models.InboundFee]()
if req.InboundFee != nil {
inboundFee = fn.Some(models.InboundFee{
Base: req.InboundFee.BaseFeeMsat,
Rate: req.InboundFee.FeeRatePpm,
})
}
baseFeeMsat := lnwire.MilliSatoshi(req.BaseFeeMsat)
feeSchema := routing.FeeSchema{
BaseFee: baseFeeMsat,
FeeRate: feeRateFixed,
InboundFee: models.InboundFee{
Base: req.InboundBaseFeeMsat,
Rate: req.InboundFeeRatePpm,
},
BaseFee: baseFeeMsat,
FeeRate: feeRateFixed,
InboundFee: inboundFee,
}
maxHtlc := lnwire.MilliSatoshi(req.MaxHtlcMsat)