htlcswitch: respect minimum relay fee
When channels fee rates are being considered for an update, the minimum relay fee should also be considered.
This commit is contained in:
5 files changed
+317
-60
No files matched your search
+69
-10
@@ -6992,14 +6992,11 @@ func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amou
|
||||
// MaxFeeRate returns the maximum fee rate given an allocation of the channel
|
||||
// initiator's spendable balance along with the local reserve amount. This can
|
||||
// be useful to determine when we should stop proposing fee updates that exceed
|
||||
// our maximum allocation. We also take a fee rate cap that should be used for
|
||||
// anchor type channels.
|
||||
// our maximum allocation.
|
||||
//
|
||||
// NOTE: This should only be used for channels in which the local commitment is
|
||||
// the initiator.
|
||||
func (lc *LightningChannel) MaxFeeRate(maxAllocation float64,
|
||||
maxAnchorFeeRate chainfee.SatPerKWeight) chainfee.SatPerKWeight {
|
||||
|
||||
func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWeight {
|
||||
lc.RLock()
|
||||
defer lc.RUnlock()
|
||||
|
||||
@@ -7017,16 +7014,78 @@ func (lc *LightningChannel) MaxFeeRate(maxAllocation float64,
|
||||
|
||||
// Ensure the fee rate doesn't dip below the fee floor.
|
||||
maxFeeRate := maxFee / (float64(weight) / 1000)
|
||||
feeRate := chainfee.SatPerKWeight(
|
||||
return chainfee.SatPerKWeight(
|
||||
math.Max(maxFeeRate, float64(chainfee.FeePerKwFloor)),
|
||||
)
|
||||
}
|
||||
|
||||
// Cap anchor fee rates.
|
||||
if lc.channelState.ChanType.HasAnchors() && feeRate > maxAnchorFeeRate {
|
||||
return maxAnchorFeeRate
|
||||
// IdealCommitFeeRate uses the current network fee, the minimum relay fee,
|
||||
// maximum fee allocation and anchor channel commitment fee rate to determine
|
||||
// the ideal fee to be used for the commitments of the channel.
|
||||
func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate,
|
||||
maxAnchorCommitFeeRate chainfee.SatPerKWeight,
|
||||
maxFeeAlloc float64) chainfee.SatPerKWeight {
|
||||
|
||||
// Get the maximum fee rate that we can use given our max fee allocation
|
||||
// and given the local reserve balance that we must preserve.
|
||||
maxFeeRate := lc.MaxFeeRate(maxFeeAlloc)
|
||||
|
||||
var commitFeeRate chainfee.SatPerKWeight
|
||||
|
||||
// If the channel has anchor outputs then cap the fee rate at the
|
||||
// max anchor fee rate if that maximum is less than our max fee rate.
|
||||
// Otherwise, cap the fee rate at the max fee rate.
|
||||
switch lc.channelState.ChanType.HasAnchors() &&
|
||||
maxFeeRate > maxAnchorCommitFeeRate {
|
||||
|
||||
case true:
|
||||
commitFeeRate = chainfee.SatPerKWeight(
|
||||
math.Min(
|
||||
float64(netFeeRate),
|
||||
float64(maxAnchorCommitFeeRate),
|
||||
),
|
||||
)
|
||||
|
||||
case false:
|
||||
commitFeeRate = chainfee.SatPerKWeight(
|
||||
math.Min(float64(netFeeRate), float64(maxFeeRate)),
|
||||
)
|
||||
}
|
||||
|
||||
return feeRate
|
||||
if commitFeeRate >= minRelayFeeRate {
|
||||
return commitFeeRate
|
||||
}
|
||||
|
||||
// The commitment fee rate is below the minimum relay fee rate.
|
||||
// If the min relay fee rate is still below the maximum fee, then use
|
||||
// the minimum relay fee rate.
|
||||
if minRelayFeeRate <= maxFeeRate {
|
||||
return minRelayFeeRate
|
||||
}
|
||||
|
||||
// The minimum relay fee rate is more than the ideal maximum fee rate.
|
||||
// Check if it is smaller than the absolute maximum fee rate we can
|
||||
// use. If it is, then we use the minimum relay fee rate and we log a
|
||||
// warning to indicate that the max channel fee allocation option was
|
||||
// ignored.
|
||||
absoluteMaxFee := lc.MaxFeeRate(1)
|
||||
if minRelayFeeRate <= absoluteMaxFee {
|
||||
lc.log.Warn("Ignoring max channel fee allocation to " +
|
||||
"ensure that the commitment fee is above the " +
|
||||
"minimum relay fee.")
|
||||
|
||||
return minRelayFeeRate
|
||||
}
|
||||
|
||||
// The absolute maximum fee rate we can pay is below the minimum
|
||||
// relay fee rate. The commitment tx will not be able to propagate.
|
||||
// To give the transaction the best chance, we use the absolute
|
||||
// maximum fee we have available and we log an error.
|
||||
lc.log.Errorf("The commitment fee rate of %s is below the current "+
|
||||
"minimum relay fee rate of %s. The max fee rate of %s will be"+
|
||||
"used.", commitFeeRate, minRelayFeeRate, absoluteMaxFee)
|
||||
|
||||
return absoluteMaxFee
|
||||
}
|
||||
|
||||
// RemoteNextRevocation returns the channelState's RemoteNextRevocation.
|
||||
|
||||
+174
-29
@@ -8005,12 +8005,10 @@ func TestForceCloseBorkedState(t *testing.T) {
|
||||
func TestChannelMaxFeeRate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assertMaxFeeRate := func(c *LightningChannel,
|
||||
maxAlloc float64, anchorMax, expFeeRate chainfee.SatPerKWeight) {
|
||||
assertMaxFeeRate := func(c *LightningChannel, maxAlloc float64,
|
||||
expFeeRate chainfee.SatPerKWeight) {
|
||||
|
||||
t.Helper()
|
||||
|
||||
maxFeeRate := c.MaxFeeRate(maxAlloc, anchorMax)
|
||||
maxFeeRate := c.MaxFeeRate(maxAlloc)
|
||||
if maxFeeRate != expFeeRate {
|
||||
t.Fatalf("expected max fee rate of %v with max "+
|
||||
"allocation of %v, got %v", expFeeRate,
|
||||
@@ -8025,14 +8023,9 @@ func TestChannelMaxFeeRate(t *testing.T) {
|
||||
// propertyTest tests that the validateFeeRate function always passes
|
||||
// for the output returned by MaxFeeRate for any valid random inputs
|
||||
// fed to MaxFeeRate.
|
||||
propertyTest := func(c *LightningChannel) func(alloc maxAlloc,
|
||||
anchorMax fee) bool {
|
||||
|
||||
return func(ma maxAlloc, anchorMax fee) bool {
|
||||
maxFeeRate := c.MaxFeeRate(
|
||||
float64(ma), chainfee.SatPerKWeight(anchorMax),
|
||||
)
|
||||
|
||||
propertyTest := func(c *LightningChannel) func(alloc maxAlloc) bool {
|
||||
return func(ma maxAlloc) bool {
|
||||
maxFeeRate := c.MaxFeeRate(float64(ma))
|
||||
return c.validateFeeRate(maxFeeRate) == nil
|
||||
}
|
||||
}
|
||||
@@ -8045,18 +8038,19 @@ func TestChannelMaxFeeRate(t *testing.T) {
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
if err = quick.Check(propertyTest(aliceChannel), nil); err != nil {
|
||||
if err := quick.Check(propertyTest(aliceChannel), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertMaxFeeRate(aliceChannel, 1.0, 0, 676794154)
|
||||
assertMaxFeeRate(aliceChannel, 0.001, 0, 676794)
|
||||
assertMaxFeeRate(aliceChannel, 0.000001, 0, 676)
|
||||
assertMaxFeeRate(aliceChannel, 0.0000001, 0, chainfee.FeePerKwFloor)
|
||||
assertMaxFeeRate(aliceChannel, 1.0, 676794154)
|
||||
assertMaxFeeRate(aliceChannel, 0.001, 676794)
|
||||
assertMaxFeeRate(aliceChannel, 0.000001, 676)
|
||||
assertMaxFeeRate(aliceChannel, 0.0000001, chainfee.FeePerKwFloor)
|
||||
|
||||
// Check that anchor channels are capped at their max fee rate.
|
||||
anchorChannel, _, cleanUp, err := CreateTestChannels(
|
||||
channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit,
|
||||
channeldb.SingleFunderTweaklessBit |
|
||||
channeldb.AnchorOutputsBit | channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
@@ -8069,16 +8063,167 @@ func TestChannelMaxFeeRate(t *testing.T) {
|
||||
|
||||
// Anchor commitments are heavier, hence will the same allocation lead
|
||||
// to slightly lower fee rates.
|
||||
assertMaxFeeRate(
|
||||
anchorChannel, 1.0, chainfee.FeePerKwFloor,
|
||||
chainfee.FeePerKwFloor,
|
||||
)
|
||||
assertMaxFeeRate(anchorChannel, 0.001, 1000000, 435941)
|
||||
assertMaxFeeRate(anchorChannel, 0.001, 300000, 300000)
|
||||
assertMaxFeeRate(anchorChannel, 0.000001, 700, 435)
|
||||
assertMaxFeeRate(
|
||||
anchorChannel, 0.0000001, 1000000, chainfee.FeePerKwFloor,
|
||||
)
|
||||
assertMaxFeeRate(anchorChannel, 1.0, 435941555)
|
||||
assertMaxFeeRate(anchorChannel, 0.001, 435941)
|
||||
assertMaxFeeRate(anchorChannel, 0.000001, 435)
|
||||
assertMaxFeeRate(anchorChannel, 0.0000001, chainfee.FeePerKwFloor)
|
||||
}
|
||||
|
||||
// TestIdealCommitFeeRate tests that we correctly compute the ideal commitment
|
||||
// fee of a channel given the current network fee, minimum relay fee, maximum
|
||||
// fee allocation and whether the channel has anchor outputs.
|
||||
func TestIdealCommitFeeRate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assertIdealFeeRate := func(c *LightningChannel, netFee, minRelay,
|
||||
maxAnchorCommit chainfee.SatPerKWeight,
|
||||
maxFeeAlloc float64, expectedFeeRate chainfee.SatPerKWeight) {
|
||||
|
||||
feeRate := c.IdealCommitFeeRate(
|
||||
netFee, minRelay, maxAnchorCommit, maxFeeAlloc,
|
||||
)
|
||||
if feeRate != expectedFeeRate {
|
||||
t.Fatalf("expected fee rate of %v got %v",
|
||||
expectedFeeRate, feeRate)
|
||||
}
|
||||
|
||||
if err := c.validateFeeRate(feeRate); err != nil {
|
||||
t.Fatalf("fee rate validation failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// propertyTest tests that the validateFeeRate function always passes
|
||||
// for the output returned by IdealCommitFeeRate for any valid random
|
||||
// inputs fed to IdealCommitFeeRate.
|
||||
propertyTest := func(c *LightningChannel) func(ma maxAlloc,
|
||||
netFee, minRelayFee, maxAnchorFee fee) bool {
|
||||
|
||||
return func(ma maxAlloc, netFee, minRelayFee,
|
||||
maxAnchorFee fee) bool {
|
||||
|
||||
idealFeeRate := c.IdealCommitFeeRate(
|
||||
chainfee.SatPerKWeight(netFee),
|
||||
chainfee.SatPerKWeight(minRelayFee),
|
||||
chainfee.SatPerKWeight(maxAnchorFee),
|
||||
float64(ma),
|
||||
)
|
||||
|
||||
return c.validateFeeRate(idealFeeRate) == nil
|
||||
}
|
||||
}
|
||||
|
||||
// Test ideal fee rates for a non-anchor channel
|
||||
t.Run("non-anchor-channel", func(t *testing.T) {
|
||||
aliceChannel, _, cleanUp, err := CreateTestChannels(
|
||||
channeldb.SingleFunderTweaklessBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
err = quick.Check(propertyTest(aliceChannel), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// If the maximum fee is lower than the network fee and above
|
||||
// the min relay fee, use the maximum fee.
|
||||
assertIdealFeeRate(
|
||||
aliceChannel, 700000000, 0, 0, 1.0, 676794154,
|
||||
)
|
||||
|
||||
// If the network fee is lower than the max fee and above the
|
||||
// min relay fee, use the network fee.
|
||||
assertIdealFeeRate(
|
||||
aliceChannel, 500000000, 0, 0, 1.0, 500000000,
|
||||
)
|
||||
|
||||
// If the fee is below the minimum relay fee, and the min relay
|
||||
// fee is less than our max fee, use the minimum relay fee.
|
||||
assertIdealFeeRate(
|
||||
aliceChannel, 500000000, 600000000, 0, 1.0, 600000000,
|
||||
)
|
||||
|
||||
// The absolute maximum fee rate we can pay (ie, using a max
|
||||
// allocation of 1) is still below the minimum relay fee. In
|
||||
// this case, use the absolute max fee.
|
||||
assertIdealFeeRate(
|
||||
aliceChannel, 800000000, 700000000, 0, 0.001,
|
||||
676794154,
|
||||
)
|
||||
})
|
||||
|
||||
// Test ideal fee rates for an anchor channel
|
||||
t.Run("anchor-channel", func(t *testing.T) {
|
||||
anchorChannel, _, cleanUp, err := CreateTestChannels(
|
||||
channeldb.SingleFunderTweaklessBit |
|
||||
channeldb.AnchorOutputsBit |
|
||||
channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
defer cleanUp()
|
||||
|
||||
err = quick.Check(propertyTest(anchorChannel), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Anchor commitments are heavier, hence the same allocation
|
||||
// leads to slightly lower fee rates.
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 0, chainfee.FeePerKwFloor,
|
||||
0.1, chainfee.FeePerKwFloor,
|
||||
)
|
||||
|
||||
// If the maximum fee is lower than the network fee, use the
|
||||
// maximum fee.
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 0, 1000000, 0.001, 435941,
|
||||
)
|
||||
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 0, 700, 0.000001, 435,
|
||||
)
|
||||
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 0, 1000000, 0.0000001,
|
||||
chainfee.FeePerKwFloor,
|
||||
)
|
||||
|
||||
// If the maximum anchor commit fee rate is less than the
|
||||
// maximum fee, use the max anchor commit fee since this is an
|
||||
// anchor channel.
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 0, 400000, 0.001, 400000,
|
||||
)
|
||||
|
||||
// If the minimum relay fee is above the max anchor commitment
|
||||
// fee rate but still below our max fee rate then use the min
|
||||
// relay fee.
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 400000, 300000, 0.001,
|
||||
400000,
|
||||
)
|
||||
|
||||
// If the min relay fee is above the ideal max fee rate but is
|
||||
// below the max fee rate when the max fee allocation is set to
|
||||
// 1, the minimum relay fee is used.
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 500000, 300000, 0.001,
|
||||
500000,
|
||||
)
|
||||
|
||||
// The absolute maximum fee rate we can pay (ie, using a max
|
||||
// allocation of 1) is still below the minimum relay fee. In
|
||||
// this case, use the absolute max fee.
|
||||
assertIdealFeeRate(
|
||||
anchorChannel, 700000000, 450000000, 300000, 0.001,
|
||||
435941555,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
type maxAlloc float64
|
||||
|
||||
Reference in new issue
Block a user