lnrpc: fix the existing routing fee inaccuracy
When updating the channel routing policy, we encounter an inaccurate precision error when calculating the routing fee. The issue stems from the way the IEEE 754 standard works. The solution here is to add a uint64 parameter (as mentioned in the issue) and keep the float64 fee_rate parameter but rounding the product of the base and fee rate.
This commit is contained in:
1 parent
234fdc6c9c
commit
ef1eff1058
6 files changed
+837
-770
No files matched your search
+22
-3
@@ -1971,7 +1971,16 @@ var updateChannelPolicyCommand = cli.Command{
|
||||
Usage: "the fee rate that will be charged " +
|
||||
"proportionally based on the value of each " +
|
||||
"forwarded HTLC, the lowest possible rate is 0 " +
|
||||
"with a granularity of 0.000001 (millionths)",
|
||||
"with a granularity of 0.000001 (millionths). Can not " +
|
||||
"be set at the same time as fee_rate_ppm.",
|
||||
},
|
||||
cli.Uint64Flag{
|
||||
Name: "fee_rate_ppm",
|
||||
Usage: "the fee rate ppm (parts per million) that " +
|
||||
"will be charged proportionally based on the value of each " +
|
||||
"forwarded HTLC, the lowest possible rate is 0 " +
|
||||
"with a granularity of 0.000001 (millionths). Can not " +
|
||||
"be set at the same time as fee_rate.",
|
||||
},
|
||||
cli.Int64Flag{
|
||||
Name: "time_lock_delta",
|
||||
@@ -2032,6 +2041,7 @@ func updateChannelPolicy(ctx *cli.Context) error {
|
||||
var (
|
||||
baseFee int64
|
||||
feeRate float64
|
||||
feeRatePpm uint64
|
||||
timeLockDelta int64
|
||||
err error
|
||||
)
|
||||
@@ -2051,8 +2061,12 @@ func updateChannelPolicy(ctx *cli.Context) error {
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctx.IsSet("fee_rate") && ctx.IsSet("fee_rate_ppm"):
|
||||
return fmt.Errorf("fee_rate or fee_rate_ppm can not both be set")
|
||||
case ctx.IsSet("fee_rate"):
|
||||
feeRate = ctx.Float64("fee_rate")
|
||||
case ctx.IsSet("fee_rate_ppm"):
|
||||
feeRatePpm = ctx.Uint64("fee_rate_ppm")
|
||||
case args.Present():
|
||||
feeRate, err = strconv.ParseFloat(args.First(), 64)
|
||||
if err != nil {
|
||||
@@ -2061,7 +2075,7 @@ func updateChannelPolicy(ctx *cli.Context) error {
|
||||
|
||||
args = args.Tail()
|
||||
default:
|
||||
return fmt.Errorf("fee_rate argument missing")
|
||||
return fmt.Errorf("fee_rate or fee_rate_ppm argument missing")
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -2100,7 +2114,6 @@ func updateChannelPolicy(ctx *cli.Context) error {
|
||||
|
||||
req := &lnrpc.PolicyUpdateRequest{
|
||||
BaseFeeMsat: baseFee,
|
||||
FeeRate: feeRate,
|
||||
TimeLockDelta: uint32(timeLockDelta),
|
||||
MaxHtlcMsat: ctx.Uint64("max_htlc_msat"),
|
||||
}
|
||||
@@ -2120,6 +2133,12 @@ func updateChannelPolicy(ctx *cli.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
if feeRate != 0 {
|
||||
req.FeeRate = feeRate
|
||||
} else if feeRatePpm != 0 {
|
||||
req.FeeRatePpm = uint32(feeRatePpm)
|
||||
}
|
||||
|
||||
resp, err := client.UpdateChannelPolicy(ctxc, req)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -88,6 +88,10 @@ Postgres](https://github.com/lightningnetwork/lnd/pull/6111)
|
||||
* [Fix missing label on streamed
|
||||
transactions](https://github.com/lightningnetwork/lnd/pull/5854).
|
||||
|
||||
* [The `fee_rate_ppm` parameter/argument was added to
|
||||
update channel policy](https://github.com/lightningnetwork/lnd/pull/5711)
|
||||
to prevent truncation error with tiny fee rates.
|
||||
|
||||
* [Closing txid is now
|
||||
exposed](https://github.com/lightningnetwork/lnd/pull/6146) inside
|
||||
WaitingCloseResp from calling `PendingChannels`.
|
||||
@@ -114,6 +118,7 @@ Postgres](https://github.com/lightningnetwork/lnd/pull/6111)
|
||||
* Bjarne Magnussen
|
||||
* Daniel McNally
|
||||
* Elle Mouton
|
||||
* Erik Ek
|
||||
* Harsha Goli
|
||||
* Joost Jager
|
||||
* Martin Habovštiak
|
||||
|
||||
+761
-750
File diff suppressed because it is too large.
Load diff
@@ -3751,6 +3751,9 @@ message PolicyUpdateRequest {
|
||||
// goes up to 6 decimal places, so 1e-6.
|
||||
double fee_rate = 4;
|
||||
|
||||
// The effective fee rate in micro-satoshis (parts per million).
|
||||
uint32 fee_rate_ppm = 9;
|
||||
|
||||
// The required timelock delta for HTLCs forwarded over the channel.
|
||||
uint32 time_lock_delta = 5;
|
||||
|
||||
|
||||
@@ -5767,6 +5767,11 @@
|
||||
"format": "double",
|
||||
"description": "The effective fee rate in milli-satoshis. The precision of this value\ngoes up to 6 decimal places, so 1e-6."
|
||||
},
|
||||
"fee_rate_ppm": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "The effective fee rate in micro-satoshis (parts per million)."
|
||||
},
|
||||
"time_lock_delta": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
|
||||
+41
-17
@@ -6297,7 +6297,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
|
||||
// Nodes on the network advertise their fee rate using this point as a base.
|
||||
// This means that the minimal possible fee rate if 1e-6, or 0.000001, or
|
||||
// 0.0001%.
|
||||
const feeBase = 1000000
|
||||
const feeBase float64 = 1000000
|
||||
|
||||
// FeeReport allows the caller to obtain a report detailing the current fee
|
||||
// schedule enforced by the node globally for each channel.
|
||||
@@ -6330,7 +6330,7 @@ func (r *rpcServer) FeeReport(ctx context.Context,
|
||||
// 1mil mSAT sent, so will divide by this to get the proper fee
|
||||
// rate.
|
||||
feeRateFixedPoint := edgePolicy.FeeProportionalMillionths
|
||||
feeRate := float64(feeRateFixedPoint) / float64(feeBase)
|
||||
feeRate := float64(feeRateFixedPoint) / feeBase
|
||||
|
||||
// TODO(roasbeef): also add stats for revenue for each channel
|
||||
feeReports = append(feeReports, &lnrpc.ChannelFeeReport{
|
||||
@@ -6470,28 +6470,52 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
|
||||
return nil, fmt.Errorf("unknown scope: %v", scope)
|
||||
}
|
||||
|
||||
var feeRateFixed uint32
|
||||
|
||||
switch {
|
||||
// As a sanity check, if the fee isn't zero, we'll ensure that the
|
||||
// passed fee rate is below 1e-6, or the lowest allowed non-zero fee
|
||||
// rate expressible within the protocol.
|
||||
case req.FeeRate != 0 && req.FeeRate < minFeeRate:
|
||||
return nil, fmt.Errorf("fee rate of %v is too small, min fee "+
|
||||
"rate is %v", req.FeeRate, minFeeRate)
|
||||
// The request should use either the fee rate in percent, or the new
|
||||
// ppm rate, but not both.
|
||||
case req.FeeRate != 0 && req.FeeRatePpm != 0:
|
||||
errMsg := "cannot set both FeeRate and FeeRatePpm at the " +
|
||||
"same time"
|
||||
|
||||
return nil, status.Errorf(codes.InvalidArgument, errMsg)
|
||||
|
||||
// If the request is using fee_rate.
|
||||
case req.FeeRate != 0:
|
||||
// As a sanity check, if the fee isn't zero, we'll ensure that
|
||||
// the passed fee rate is below 1e-6, or the lowest allowed
|
||||
// non-zero fee rate expressible within the protocol.
|
||||
if req.FeeRate != 0 && req.FeeRate < minFeeRate {
|
||||
return nil, fmt.Errorf("fee rate of %v is too "+
|
||||
"small, min fee rate is %v", req.FeeRate,
|
||||
minFeeRate)
|
||||
}
|
||||
|
||||
// We'll also need to convert the floating point fee rate we
|
||||
// accept over RPC to the fixed point rate that we use within
|
||||
// the protocol. We do this by multiplying the passed fee rate
|
||||
// by the fee base. This gives us the fixed point, scaled by 1
|
||||
// million that's used within the protocol.
|
||||
//
|
||||
// Because of the inaccurate precision of the IEEE 754
|
||||
// standard, we need to round the product of feerate and
|
||||
// feebase.
|
||||
feeRateFixed = uint32(math.Round(req.FeeRate * feeBase))
|
||||
|
||||
// Otherwise, we use the fee_rate_ppm parameter.
|
||||
case req.FeeRatePpm != 0:
|
||||
feeRateFixed = req.FeeRatePpm
|
||||
}
|
||||
|
||||
// We'll also ensure that the user isn't setting a CLTV delta that
|
||||
// won't give outgoing HTLCs enough time to fully resolve if needed.
|
||||
case req.TimeLockDelta < minTimeLockDelta:
|
||||
if req.TimeLockDelta < minTimeLockDelta {
|
||||
return nil, fmt.Errorf("time lock delta of %v is too small, "+
|
||||
"minimum supported is %v", req.TimeLockDelta,
|
||||
minTimeLockDelta)
|
||||
}
|
||||
|
||||
// We'll also need to convert the floating point fee rate we accept
|
||||
// over RPC to the fixed point rate that we use within the protocol. We
|
||||
// do this by multiplying the passed fee rate by the fee base. This
|
||||
// gives us the fixed point, scaled by 1 million that's used within the
|
||||
// protocol.
|
||||
feeRateFixed := uint32(req.FeeRate * feeBase)
|
||||
baseFeeMsat := lnwire.MilliSatoshi(req.BaseFeeMsat)
|
||||
feeSchema := routing.FeeSchema{
|
||||
BaseFee: baseFeeMsat,
|
||||
@@ -6513,9 +6537,9 @@ func (r *rpcServer) UpdateChannelPolicy(ctx context.Context,
|
||||
}
|
||||
|
||||
rpcsLog.Debugf("[updatechanpolicy] updating channel policy base_fee=%v, "+
|
||||
"rate_float=%v, rate_fixed=%v, time_lock_delta: %v, "+
|
||||
"rate_fixed=%v, time_lock_delta: %v, "+
|
||||
"min_htlc=%v, max_htlc=%v, targets=%v",
|
||||
req.BaseFeeMsat, req.FeeRate, feeRateFixed, req.TimeLockDelta,
|
||||
req.BaseFeeMsat, feeRateFixed, req.TimeLockDelta,
|
||||
minHtlc, maxHtlc,
|
||||
spew.Sdump(targetChans))
|
||||
|
||||
|
||||
Reference in new issue
Block a user