Merge pull request #7805 from ziggie1984/updatefee-limiter
Limit FeeRate change for the UpdateFee msg to prevent sharp changes
This commit is contained in:
4 files changed
+403
-173
No files matched your search
@@ -56,7 +56,7 @@
|
||||
* [Fixed](https://github.com/lightningnetwork/lnd/pull/8096) a case where `lnd`
|
||||
might dip below its channel reserve when htlcs are added concurrently. A
|
||||
fee buffer (additional balance) is now always kept on the local side ONLY
|
||||
if the channel was opened locally. This is in accordance with the BOTL 02
|
||||
if the channel was opened locally. This is in accordance with the BOLT 02
|
||||
specification and protects against sharp fee changes because there is always
|
||||
this buffer which can be used to increase the commitment fee and it also
|
||||
protects against the case where htlcs are added asynchronously resulting in
|
||||
@@ -90,10 +90,18 @@
|
||||
precision issue when querying payments and invoices using the start and end
|
||||
date filters.
|
||||
|
||||
<<<<<<< HEAD
|
||||
* [Fixed](https://github.com/lightningnetwork/lnd/pull/8496) an issue where
|
||||
`locked_balance` is not updated in `WalletBalanceResponse` when outputs are
|
||||
reserved for `OpenChannel` by using non-volatile leases instead of volatile
|
||||
locks.
|
||||
=======
|
||||
* [Fixed](https://github.com/lightningnetwork/lnd/pull/7805) a case where `lnd`
|
||||
might propose a low fee rate for the channel (when initiator) due to the
|
||||
mempool not having enough data yet or the channel might be drained locally
|
||||
which with the default fee allocation in place will eventually lead to the
|
||||
downsizing to the fee floor (1 sat/vbyte) in the worst case.
|
||||
>>>>>>> ae6f11c56 (docs: add release-notes.)
|
||||
|
||||
# New Features
|
||||
## Functional Enhancements
|
||||
@@ -328,7 +336,7 @@
|
||||
option.
|
||||
|
||||
* [Update min_final_cltv_expiry_delta](https://github.com/lightningnetwork/lnd/pull/8308).
|
||||
This only effects external invoices which do not supply the
|
||||
This only affects external invoices which do not supply the
|
||||
min_final_cltv_expiry parameter. LND has NOT allowed the creation of invoices
|
||||
with a lower min_final_cltv_expiry_delta value than 18 blocks since
|
||||
LND 0.11.0.
|
||||
|
||||
@@ -311,6 +311,8 @@ type BitcoindEstimator struct {
|
||||
// to "CONSERVATIVE".
|
||||
feeMode string
|
||||
|
||||
// TODO(ziggie): introduce an interface for the client to enhance
|
||||
// testability of the estimator.
|
||||
bitcoindConn *rpcclient.Client
|
||||
}
|
||||
|
||||
@@ -472,6 +474,13 @@ func (b *BitcoindEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Bitcoind will not report any fee estimation if it has not enough
|
||||
// data available hence the fee will remain zero. We return an error
|
||||
// here to make sure that we do not use the min relay fee instead.
|
||||
if satPerKB == 0 {
|
||||
return 0, fmt.Errorf("fee estimation data not available yet")
|
||||
}
|
||||
|
||||
// Since we use fee rates in sat/kw internally, we'll convert the
|
||||
// estimated fee rate from its sat/kb representation to sat/kw.
|
||||
satPerKw := SatPerKVByte(satPerKB).FeePerKWeight()
|
||||
@@ -479,14 +488,14 @@ func (b *BitcoindEstimator) fetchEstimate(confTarget uint32) (SatPerKWeight, err
|
||||
// Finally, we'll enforce our fee floor.
|
||||
minRelayFee := b.minFeeManager.fetchMinFee()
|
||||
if satPerKw < minRelayFee {
|
||||
log.Debugf("Estimated fee rate of %v sat/kw is too low, "+
|
||||
"using fee floor of %v sat/kw instead", satPerKw,
|
||||
log.Debugf("Estimated fee rate of %v is too low, "+
|
||||
"using fee floor of %v instead", satPerKw,
|
||||
minRelayFee)
|
||||
|
||||
satPerKw = minRelayFee
|
||||
}
|
||||
|
||||
log.Debugf("Returning %v sat/kw for conf target of %v",
|
||||
log.Debugf("Returning %v for conf target of %v",
|
||||
int64(satPerKw), confTarget)
|
||||
|
||||
return satPerKw, nil
|
||||
|
||||
+26
-11
@@ -7128,6 +7128,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
localDelayTweak := input.SingleTweakBytes(
|
||||
keyRing.CommitPoint, localChanCfg.DelayBasePoint.PubKey,
|
||||
)
|
||||
|
||||
return &OutgoingHtlcResolution{
|
||||
Expiry: htlc.RefundTimeout,
|
||||
SignedTimeoutTx: timeoutTx,
|
||||
@@ -8663,10 +8664,17 @@ func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amou
|
||||
// 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.
|
||||
// Moreover it returns the share of the total balance in the range of [0,1]
|
||||
// which can be allocated to fees. When our desired fee allocation would lead to
|
||||
// a maximum fee rate below the current commitment fee rate we floor the maximum
|
||||
// at the current fee rate which leads to different fee allocations than
|
||||
// initially requested via `maxAllocation`.
|
||||
//
|
||||
// NOTE: This should only be used for channels in which the local commitment is
|
||||
// the initiator.
|
||||
func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWeight {
|
||||
func (lc *LightningChannel) MaxFeeRate(
|
||||
maxAllocation float64) (chainfee.SatPerKWeight, float64) {
|
||||
|
||||
lc.RLock()
|
||||
defer lc.RUnlock()
|
||||
|
||||
@@ -8676,18 +8684,25 @@ func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWe
|
||||
// exactly why it was introduced to react for sharp fee changes.
|
||||
availableBalance, weight := lc.availableBalance(AdditionalHtlc)
|
||||
|
||||
oldFee := lc.localCommitChain.tip().feePerKw.FeeForWeight(weight)
|
||||
currentFee := lc.localCommitChain.tip().feePerKw.FeeForWeight(weight)
|
||||
|
||||
// baseBalance is the maximum amount available for us to spend on fees.
|
||||
baseBalance := availableBalance.ToSatoshis() + oldFee
|
||||
baseBalance := availableBalance.ToSatoshis() + currentFee
|
||||
|
||||
maxFee := float64(baseBalance) * maxAllocation
|
||||
// In case our local channel balance is drained, we make sure we do not
|
||||
// decrease the fee rate below the current fee rate. This could lead to
|
||||
// a scenario where we lower the commitment fee rate as low as the fee
|
||||
// floor although current fee rates are way higher. The maximum fee
|
||||
// we allow should not be smaller then the current fee. The decrease
|
||||
// in fee rate should happen when the mempool reports lower fee levels
|
||||
// rather than us decreasing in local balance. The max fee rate is
|
||||
// always floored by the current fee rate of the channel.
|
||||
idealMaxFee := float64(baseBalance) * maxAllocation
|
||||
maxFee := math.Max(float64(currentFee), idealMaxFee)
|
||||
maxFeeAllocation := maxFee / float64(baseBalance)
|
||||
maxFeeRate := chainfee.SatPerKWeight(maxFee / (float64(weight) / 1000))
|
||||
|
||||
// Ensure the fee rate doesn't dip below the fee floor.
|
||||
maxFeeRate := maxFee / (float64(weight) / 1000)
|
||||
return chainfee.SatPerKWeight(
|
||||
math.Max(maxFeeRate, float64(chainfee.FeePerKwFloor)),
|
||||
)
|
||||
return maxFeeRate, maxFeeAllocation
|
||||
}
|
||||
|
||||
// IdealCommitFeeRate uses the current network fee, the minimum relay fee,
|
||||
@@ -8699,7 +8714,7 @@ func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate,
|
||||
|
||||
// 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)
|
||||
maxFeeRate, _ := lc.MaxFeeRate(maxFeeAlloc)
|
||||
|
||||
var commitFeeRate chainfee.SatPerKWeight
|
||||
|
||||
@@ -8738,7 +8753,7 @@ func (lc *LightningChannel) IdealCommitFeeRate(netFeeRate, minRelayFeeRate,
|
||||
// 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)
|
||||
absoluteMaxFee, _ := lc.MaxFeeRate(1)
|
||||
if minRelayFeeRate <= absoluteMaxFee {
|
||||
lc.log.Warn("Ignoring max channel fee allocation to " +
|
||||
"ensure that the commitment fee is above the " +
|
||||
|
||||
+355
-157
@@ -7515,67 +7515,146 @@ func TestForceCloseBorkedState(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestChannelMaxFeeRate ensures we correctly compute a channel initiator's max
|
||||
// fee rate based on an allocation and its available balance. It should never
|
||||
// dip below the established fee floor.
|
||||
// fee rate based on an allocation and its available balance. When a very low
|
||||
// fee allocation value is selected the max fee rate is always floored at the
|
||||
// current fee rate of the channel.
|
||||
func TestChannelMaxFeeRate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assertMaxFeeRate := func(c *LightningChannel, maxAlloc float64,
|
||||
expFeeRate chainfee.SatPerKWeight) {
|
||||
|
||||
maxFeeRate := c.MaxFeeRate(maxAlloc)
|
||||
if maxFeeRate != expFeeRate {
|
||||
t.Fatalf("expected max fee rate of %v with max "+
|
||||
"allocation of %v, got %v", expFeeRate,
|
||||
maxAlloc, maxFeeRate)
|
||||
}
|
||||
|
||||
if err := c.validateFeeRate(maxFeeRate); err != nil {
|
||||
t.Fatalf("fee rate validation failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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) bool {
|
||||
return func(ma maxAlloc) bool {
|
||||
maxFeeRate := c.MaxFeeRate(float64(ma))
|
||||
maxFeeRate, _ := c.MaxFeeRate(float64(ma))
|
||||
return c.validateFeeRate(maxFeeRate) == nil
|
||||
}
|
||||
}
|
||||
|
||||
aliceChannel, _, err := CreateTestChannels(
|
||||
// Create a non-anchor and an anchor channel setup.
|
||||
nonAnchorChannel, _, err := CreateTestChannels(
|
||||
t, channeldb.SingleFunderTweaklessBit,
|
||||
)
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
if err := quick.Check(propertyTest(aliceChannel), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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, _, err := CreateTestChannels(
|
||||
t, channeldb.SingleFunderTweaklessBit|
|
||||
channeldb.AnchorOutputsBit|channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
require.NoError(t, err, "unable to create test channels")
|
||||
|
||||
if err = quick.Check(propertyTest(anchorChannel), nil); err != nil {
|
||||
t.Fatal(err)
|
||||
// Run the property tests for both channel types.
|
||||
err = quick.Check(propertyTest(nonAnchorChannel), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = quick.Check(propertyTest(anchorChannel), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
channel *LightningChannel
|
||||
maxFeeAlloc float64
|
||||
expectedFeeAlloc float64
|
||||
expectedMinFeeRate bool
|
||||
}{
|
||||
{
|
||||
name: "non-anchor-channel high fee alloc",
|
||||
channel: nonAnchorChannel,
|
||||
maxFeeAlloc: 1.0,
|
||||
expectedFeeAlloc: 1.0,
|
||||
},
|
||||
{
|
||||
name: "non-anchor-channel chan low fee " +
|
||||
"alloc",
|
||||
channel: nonAnchorChannel,
|
||||
maxFeeAlloc: 1e-3,
|
||||
expectedFeeAlloc: 1e-3,
|
||||
},
|
||||
{
|
||||
name: "anchor-channel high chan fee alloc",
|
||||
channel: anchorChannel,
|
||||
maxFeeAlloc: 1.0,
|
||||
expectedFeeAlloc: 1.0,
|
||||
},
|
||||
{
|
||||
name: "anchor-channel low fee alloc",
|
||||
channel: anchorChannel,
|
||||
maxFeeAlloc: 1e-3,
|
||||
expectedFeeAlloc: 1e-3,
|
||||
},
|
||||
{
|
||||
// The fee rate is capped at the current fee rate if the
|
||||
// fee allocation is too low.
|
||||
name: "non-anchor-channel current fee rate " +
|
||||
"cap",
|
||||
channel: nonAnchorChannel,
|
||||
maxFeeAlloc: 1e-6,
|
||||
expectedMinFeeRate: true,
|
||||
},
|
||||
{
|
||||
name: "non-anchor-channel current fee rate " +
|
||||
"cap",
|
||||
channel: nonAnchorChannel,
|
||||
maxFeeAlloc: 1e-8,
|
||||
expectedMinFeeRate: true,
|
||||
},
|
||||
{
|
||||
name: "anchor-channel current fee rate " +
|
||||
"cap",
|
||||
channel: anchorChannel,
|
||||
maxFeeAlloc: 1e-6,
|
||||
expectedMinFeeRate: true,
|
||||
},
|
||||
{
|
||||
name: "anchor-channel current fee rate " +
|
||||
"cap",
|
||||
channel: anchorChannel,
|
||||
maxFeeAlloc: 1e-8,
|
||||
expectedMinFeeRate: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Anchor commitments are heavier, hence will the same allocation lead
|
||||
// to slightly lower fee rates.
|
||||
assertMaxFeeRate(anchorChannel, 1.0, 435941555)
|
||||
assertMaxFeeRate(anchorChannel, 0.001, 435941)
|
||||
assertMaxFeeRate(anchorChannel, 0.000001, 435)
|
||||
assertMaxFeeRate(anchorChannel, 0.0000001, chainfee.FeePerKwFloor)
|
||||
for _, testCase := range testCases {
|
||||
tc := testCase
|
||||
|
||||
maxFeeRate, feeAllocation := tc.channel.MaxFeeRate(
|
||||
tc.maxFeeAlloc,
|
||||
)
|
||||
|
||||
currentFeeRate := chainfee.SatPerKWeight(
|
||||
tc.channel.channelState.LocalCommitment.FeePerKw,
|
||||
)
|
||||
|
||||
// When the fee allocation would push our max fee rate below our
|
||||
// current commitment fee rate of the channel we cap the max fee
|
||||
// rate at the current fee rate of the channel. There might be
|
||||
// some rounding inaccuracies due to the fee rate calculation
|
||||
// therefore we accept a relative error of 0.1%.
|
||||
if tc.expectedMinFeeRate {
|
||||
require.InEpsilonf(
|
||||
t, float64(currentFeeRate), float64(maxFeeRate),
|
||||
1e-3, "expected max fee rate:%d, got max "+
|
||||
"fee rate :%d", currentFeeRate,
|
||||
maxFeeRate,
|
||||
)
|
||||
} else {
|
||||
// When the max fee rate is not capped because there
|
||||
// is enough balance to allocate funds from we compare
|
||||
// the fee allocation rather then the max fee rate so
|
||||
// that we can reason more easily about the test values.
|
||||
// Because of floating point operations we accept
|
||||
// a relative error of 0.1%.
|
||||
require.InEpsilonf(
|
||||
t, tc.expectedFeeAlloc, feeAllocation, 1e-3,
|
||||
"expected fee allocation:%f, got fee "+
|
||||
"allocation:%f", tc.expectedFeeAlloc,
|
||||
feeAllocation,
|
||||
)
|
||||
}
|
||||
|
||||
err := tc.channel.validateFeeRate(maxFeeRate)
|
||||
require.NoErrorf(t, err, "fee rate validation failed")
|
||||
}
|
||||
}
|
||||
|
||||
// TestIdealCommitFeeRate tests that we correctly compute the ideal commitment
|
||||
@@ -7584,23 +7663,6 @@ func TestChannelMaxFeeRate(t *testing.T) {
|
||||
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.
|
||||
@@ -7621,115 +7683,251 @@ func TestIdealCommitFeeRate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test ideal fee rates for a non-anchor channel
|
||||
// Create a non-anchor channel and an anchor test channel.
|
||||
nonAnchorChannel, _, err := CreateTestChannels(
|
||||
t, channeldb.SingleFunderTweaklessBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
|
||||
anchorChannel, _, err := CreateTestChannels(
|
||||
t, channeldb.SingleFunderTweaklessBit|
|
||||
channeldb.AnchorOutputsBit|
|
||||
channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
}
|
||||
|
||||
// Run the property tests for both channel types (non-anchor and
|
||||
// anchor).
|
||||
err = quick.Check(propertyTest(nonAnchorChannel), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = quick.Check(propertyTest(anchorChannel), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// maxFeeRate is a helper function which calculates the maximum fee rate
|
||||
// a channel is allowed to allocate to fees. It does not take a minimum
|
||||
// fee rate into account.
|
||||
maxFeeRate := func(c *LightningChannel,
|
||||
maxFeeAlloc float64) chainfee.SatPerKWeight {
|
||||
|
||||
balance, weight := c.availableBalance(AdditionalHtlc)
|
||||
feeRate := c.localCommitChain.tip().feePerKw
|
||||
currentFee := feeRate.FeeForWeight(weight)
|
||||
|
||||
maxBalance := balance.ToSatoshis() + currentFee
|
||||
|
||||
maxFee := float64(maxBalance) * maxFeeAlloc
|
||||
|
||||
maxFeeRate := chainfee.SatPerKWeight(
|
||||
maxFee / (float64(weight) / 1000),
|
||||
)
|
||||
|
||||
return maxFeeRate
|
||||
}
|
||||
|
||||
// currentFeeRate calculates the current fee rate of the channel. The
|
||||
// ideal fee rate is floored at the current fee rate of the channel.
|
||||
currentFeeRate := func(c *LightningChannel) chainfee.SatPerKWeight {
|
||||
return c.localCommitChain.tip().feePerKw
|
||||
}
|
||||
|
||||
// testCase definies the test cases when calculating the ideal fee rate
|
||||
// for both channel types (non-anchor and anchor channel).
|
||||
type testCase struct {
|
||||
name string
|
||||
channel *LightningChannel
|
||||
maxFeeAlloc float64
|
||||
networkFeeRate chainfee.SatPerKWeight
|
||||
minRelayFee chainfee.SatPerKWeight
|
||||
maxAnchorCommitFee chainfee.SatPerKWeight
|
||||
expectedFeeRate chainfee.SatPerKWeight
|
||||
}
|
||||
|
||||
nonAnchorCases := []testCase{
|
||||
{
|
||||
name: "ideal fee rate capped at the max " +
|
||||
"available fee rate which is smaller than " +
|
||||
"the network fee rate",
|
||||
channel: nonAnchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1.0,
|
||||
// There is not enough balance to pay the network fee so
|
||||
// the maximum available fee rate is expected.
|
||||
expectedFeeRate: maxFeeRate(nonAnchorChannel, 1.0),
|
||||
},
|
||||
{
|
||||
name: "enough balance for the ideal network " +
|
||||
"fee rate",
|
||||
channel: nonAnchorChannel,
|
||||
networkFeeRate: 5e8,
|
||||
maxFeeAlloc: 1.0,
|
||||
expectedFeeRate: 5e8,
|
||||
},
|
||||
{
|
||||
name: "min relay fee rate is greater than " +
|
||||
"the ideal network fee rate",
|
||||
channel: nonAnchorChannel,
|
||||
networkFeeRate: 5e8,
|
||||
maxFeeAlloc: 1.0,
|
||||
minRelayFee: 6e8,
|
||||
// The min relay fee rate is used as ideal fee rate
|
||||
// because we can afford it and it's greater than the
|
||||
// network fee rate.
|
||||
expectedFeeRate: 6e8,
|
||||
},
|
||||
{
|
||||
name: "max available fee rate is smaller than the " +
|
||||
"min relay fee rate",
|
||||
channel: nonAnchorChannel,
|
||||
networkFeeRate: 8e8,
|
||||
maxFeeAlloc: 1e-3,
|
||||
minRelayFee: 7e8,
|
||||
// Using 100% of the available balance (fee alloc) for
|
||||
// the fee rate but this is still not enough to pay for
|
||||
// the min relay fee rate.
|
||||
expectedFeeRate: maxFeeRate(nonAnchorChannel, 1.0),
|
||||
},
|
||||
{
|
||||
name: "current fee rate of the channel is used as a" +
|
||||
"fee floor because fee alloc is too small " +
|
||||
"(fee ratcheting)",
|
||||
channel: nonAnchorChannel,
|
||||
networkFeeRate: 8e8,
|
||||
maxFeeAlloc: 1e-7,
|
||||
// The fee allocation is too small so we floor the ideal
|
||||
// fee rate at the current fee rate of the channel.
|
||||
expectedFeeRate: currentFeeRate(anchorChannel),
|
||||
},
|
||||
}
|
||||
|
||||
anchorCases := []testCase{
|
||||
{
|
||||
name: "fee rate is capped at the max anchor commit " +
|
||||
"fee rate, which is equal to the fee floor",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 0.1,
|
||||
maxAnchorCommitFee: chainfee.FeePerKwFloor,
|
||||
expectedFeeRate: chainfee.FeePerKwFloor,
|
||||
},
|
||||
{
|
||||
name: "fee rate capped at the max anchor commit fee " +
|
||||
"rate",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1e-6,
|
||||
maxAnchorCommitFee: 700,
|
||||
expectedFeeRate: 700,
|
||||
},
|
||||
{
|
||||
name: "fee rate is capped at the max commit anchor" +
|
||||
"fee rate",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1e-3,
|
||||
maxAnchorCommitFee: 1e5,
|
||||
expectedFeeRate: 1e5,
|
||||
},
|
||||
{
|
||||
name: "min relay fee rate is used when " +
|
||||
"the max commit anchor fee rate is smaller",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1e-4,
|
||||
minRelayFee: 4e5,
|
||||
maxAnchorCommitFee: 3e5,
|
||||
expectedFeeRate: 4e5,
|
||||
},
|
||||
{
|
||||
name: "min relay fee rate is used when " +
|
||||
"we can still can pay for it using 100% of " +
|
||||
"our balance neglecting the initial fee alloc",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1e-3,
|
||||
minRelayFee: 5e5,
|
||||
maxAnchorCommitFee: 3e5,
|
||||
expectedFeeRate: 5e5,
|
||||
},
|
||||
{
|
||||
name: "max fee rate using 100% of our balance, " +
|
||||
"neglecting the initial fee alloc, but still " +
|
||||
"not able to account for the min relay fee " +
|
||||
"rate",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1e-3,
|
||||
minRelayFee: 4.5e8,
|
||||
maxAnchorCommitFee: 3e5,
|
||||
// This is the absolute maximum balance which can be
|
||||
// used for the commitment fee.
|
||||
expectedFeeRate: maxFeeRate(anchorChannel, 1.0),
|
||||
},
|
||||
{
|
||||
name: "current fee rate of the " +
|
||||
"commitment channel is used as the max " +
|
||||
"commit anchor fee rate because the fee " +
|
||||
"alloc. is too small (fee ratcheting)",
|
||||
channel: anchorChannel,
|
||||
networkFeeRate: 7e8,
|
||||
maxFeeAlloc: 1e-7,
|
||||
maxAnchorCommitFee: 1e6,
|
||||
expectedFeeRate: currentFeeRate(anchorChannel),
|
||||
},
|
||||
}
|
||||
|
||||
assertIdealFeeRate := func(c *LightningChannel, netFee, minRelay,
|
||||
maxAnchorCommit chainfee.SatPerKWeight,
|
||||
maxFeeAlloc float64, expectedFeeRate chainfee.SatPerKWeight) {
|
||||
|
||||
feeRate := c.IdealCommitFeeRate(
|
||||
netFee, minRelay, maxAnchorCommit, maxFeeAlloc,
|
||||
)
|
||||
|
||||
// Due to rounding inaccuracies when calculating the fee rate a
|
||||
// relative error of 0.1% is tolerated.
|
||||
require.InEpsilonf(
|
||||
t, float64(expectedFeeRate), float64(feeRate),
|
||||
1e-3, "expected max fee rate:%d, got max "+
|
||||
"fee rate :%d", currentFeeRate,
|
||||
maxFeeRate,
|
||||
)
|
||||
|
||||
if err := c.validateFeeRate(feeRate); err != nil {
|
||||
t.Fatalf("fee rate validation failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("non-anchor-channel", func(t *testing.T) {
|
||||
aliceChannel, _, err := CreateTestChannels(
|
||||
t, channeldb.SingleFunderTweaklessBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
for _, testCase := range nonAnchorCases {
|
||||
tc := testCase
|
||||
|
||||
assertIdealFeeRate(
|
||||
tc.channel, tc.networkFeeRate, tc.minRelayFee,
|
||||
tc.maxAnchorCommitFee, tc.maxFeeAlloc,
|
||||
tc.expectedFeeRate,
|
||||
)
|
||||
}
|
||||
|
||||
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, _, err := CreateTestChannels(
|
||||
t, channeldb.SingleFunderTweaklessBit|
|
||||
channeldb.AnchorOutputsBit|
|
||||
channeldb.ZeroHtlcTxFeeBit,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create test channels: %v", err)
|
||||
for _, testCase := range anchorCases {
|
||||
tc := testCase
|
||||
|
||||
assertIdealFeeRate(
|
||||
tc.channel, tc.networkFeeRate, tc.minRelayFee,
|
||||
tc.maxAnchorCommitFee, tc.maxFeeAlloc,
|
||||
tc.expectedFeeRate,
|
||||
)
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user