diff --git a/lnwallet/chainfee/rates.go b/lnwallet/chainfee/rates.go index 48c3d7df..69c458a4 100644 --- a/lnwallet/chainfee/rates.go +++ b/lnwallet/chainfee/rates.go @@ -9,8 +9,13 @@ import ( const ( // FeePerKwFloor is the lowest fee rate in sat/kw that we should use for - // determining transaction fees. + // estimating transaction fees before signing. FeePerKwFloor SatPerKWeight = 253 + + // AbsoluteFeePerKwFloor is the lowest fee rate in sat/kw of a + // transaction that we should ever _create_. This is the the equivalent + // of 1 sat/byte in sat/kw. + AbsoluteFeePerKwFloor SatPerKWeight = 250 ) // SatPerKVByte represents a fee rate in sat/kb. diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 5f6eef92..600820f2 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2412,9 +2412,9 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, effFeeRate := chainfee.SatPerKWeight(fee) * 1000 / chainfee.SatPerKWeight(weight) - if effFeeRate < chainfee.FeePerKwFloor { + if effFeeRate < chainfee.AbsoluteFeePerKwFloor { return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+ - "attempts to create commitment wigh feerate %v: %v", + "attempts to create commitment with feerate %v: %v", nextHeight, lc.channelState.FundingOutpoint, effFeeRate, spew.Sdump(commitTx)) } diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 318c0b96..9f10be16 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -7638,3 +7638,41 @@ func TestChannelMaxFeeRate(t *testing.T) { assertMaxFeeRate(0.000001, 690) assertMaxFeeRate(0.0000001, chainfee.FeePerKwFloor) } + +// TestChannelFeeRateFloor asserts that valid commitments can be proposed and +// received using chainfee.FeePerKwFloor as the initiator's fee rate. +func TestChannelFeeRateFloor(t *testing.T) { + t.Parallel() + + alice, bob, cleanUp, err := CreateTestChannels( + channeldb.SingleFunderTweaklessBit, + ) + if err != nil { + t.Fatalf("unable to create test channels: %v", err) + } + defer cleanUp() + + // Set the fee rate to the proposing fee rate floor. + minFee := chainfee.FeePerKwFloor + + // Alice is the initiator, so only she can propose fee updates. + if err := alice.UpdateFee(minFee); err != nil { + t.Fatalf("unable to send fee update") + } + if err := bob.ReceiveUpdateFee(minFee); err != nil { + t.Fatalf("unable to receive fee update") + } + + // Check that alice can still sign commitments. + sig, htlcSigs, _, err := alice.SignNextCommitment() + if err != nil { + t.Fatalf("alice unable to sign commitment: %v", err) + } + + // Check that bob can still receive commitments. + err = bob.ReceiveNewCommitment(sig, htlcSigs) + if err != nil { + t.Fatalf("bob unable to process alice's new commitment: %v", + err) + } +}