From 45c6ee69d2727f052ba5b937e039423c1c267597 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 21 Jul 2023 09:53:12 +0200 Subject: [PATCH 1/3] chainfee: special case a zero fee estimation. Bitcoind will not report any fee estimation in case it has not enough data available. We used to just set the min mempool fee in such cases but this might not represent the current fee situation of the bitcoin network. We return an error now so that we will use the fallback fee instead. --- lnwallet/chainfee/estimator.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/lnwallet/chainfee/estimator.go b/lnwallet/chainfee/estimator.go index 824af3f11..c4ac1591c 100644 --- a/lnwallet/chainfee/estimator.go +++ b/lnwallet/chainfee/estimator.go @@ -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 From 6821309af34e448e6cf4ae2b3795c3127d236a70 Mon Sep 17 00:00:00 2001 From: ziggie Date: Fri, 21 Jul 2023 09:57:50 +0200 Subject: [PATCH 2/3] lnwallet: Change MaxFee calculation. When determining the max fee rate of a channel we used to scale the fee rate depending on our available local balance on this channel. This lead to a special case that if a channel would be drained we could especially decrease the fee rate even down to the fee floor. Now we make sure that our max fee rate will not be lower than the old fee rate to make sure in case our channel is locally drained we do not continue to decrease fees too low. --- lnwallet/channel.go | 37 ++- lnwallet/channel_test.go | 512 +++++++++++++++++++++++++++------------ 2 files changed, 381 insertions(+), 168 deletions(-) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index aa3f38650..17e84acec 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -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 " + diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index aa747bc42..53c93aab2 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -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, - ) }) } From 25c38491ee355d0c65d657781c4f55e2b1e0cac9 Mon Sep 17 00:00:00 2001 From: ziggie Date: Mon, 6 Nov 2023 12:13:48 +0100 Subject: [PATCH 3/3] docs: add release-notes. Adding the release-notes for release 0.18 and fixing typos in the release doc. --- docs/release-notes/release-notes-0.18.0.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/release-notes-0.18.0.md b/docs/release-notes/release-notes-0.18.0.md index adbc887e9..64cb509c5 100644 --- a/docs/release-notes/release-notes-0.18.0.md +++ b/docs/release-notes/release-notes-0.18.0.md @@ -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.