diff --git a/config.go b/config.go index d7e3aefd46..3e4f6679f5 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,7 @@ import ( "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/tor" ) @@ -289,6 +290,8 @@ type Config struct { MaxChannelFeeAllocation float64 `long:"max-channel-fee-allocation" description:"The maximum percentage of total funds that can be allocated to a channel's commitment fee. This only applies for the initiator of the channel. Valid values are within [0.1, 1]."` + MaxCommitFeeRateAnchors uint64 `long:"max-commit-fee-rate-anchors" description:"The maximum fee rate in sat/vbyte that will be used for commitments of channels of the anchors type"` + DryRunMigration bool `long:"dry-run-migration" description:"If true, lnd will abort committing a migration if it would otherwise have been successful. This leaves the database unmodified, and still compatible with the previously active version of lnd."` net tor.Net @@ -475,6 +478,7 @@ func DefaultConfig() Config { }, MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry, MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation, + MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte, LogWriter: build.NewRotatingLogWriter(), DB: lncfg.DefaultDB(), registeredChains: chainreg.NewChainRegistry(), @@ -735,6 +739,12 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) { cfg.MaxChannelFeeAllocation) } + if cfg.MaxCommitFeeRateAnchors < 1 { + return nil, fmt.Errorf("invalid max commit fee rate anchors: "+ + "%v, must be at least 1 sat/vbyte", + cfg.MaxCommitFeeRateAnchors) + } + // Validate the Tor config parameters. socks, err := lncfg.ParseAddressString( cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort), diff --git a/fundingmanager.go b/fundingmanager.go index 46338af39c..47ab186eed 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -366,6 +366,10 @@ type fundingConfig struct { // RegisteredChains keeps track of all chains that have been registered // with the daemon. RegisteredChains *chainreg.ChainRegistry + + // MaxAnchorsCommitFeeRate is the max commitment fee rate we'll use as + // the initiator for channels of the anchor type. + MaxAnchorsCommitFeeRate chainfee.SatPerKWeight } // fundingManager acts as an orchestrator/bridge between the wallet's @@ -3123,16 +3127,6 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { msg.pushAmt, msg.chainHash, peerKey.SerializeCompressed(), ourDustLimit, msg.minConfs) - // First, we'll query the fee estimator for a fee that should get the - // commitment transaction confirmed by the next few blocks (conf target - // of 3). We target the near blocks here to ensure that we'll be able - // to execute a timely unilateral channel closure if needed. - commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3) - if err != nil { - msg.err <- err - return - } - // We set the channel flags to indicate whether we want this channel to // be announced to the network. var channelFlags lnwire.FundingFlag @@ -3192,6 +3186,25 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { commitType := commitmentType( msg.peer.LocalFeatures(), msg.peer.RemoteFeatures(), ) + + // First, we'll query the fee estimator for a fee that should get the + // commitment transaction confirmed by the next few blocks (conf target + // of 3). We target the near blocks here to ensure that we'll be able + // to execute a timely unilateral channel closure if needed. + commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3) + if err != nil { + msg.err <- err + return + } + + // For anchor channels cap the initial commit fee rate at our defined + // maximum. + if commitType == lnwallet.CommitmentTypeAnchors && + commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate { + + commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate + } + req := &lnwallet.InitFundingReserveMsg{ ChainHash: &msg.chainHash, PendingChanID: chanID, @@ -3224,6 +3237,36 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // SubtractFees=true. capacity := reservation.Capacity() + // We'll use the current value of the channels and our default policy + // to determine of required commitment constraints for the remote + // party. + chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit) + + // As a sanity check, we ensure that the user is not trying to open a + // tiny anchor channel that would be unusable because of the fee siphon + // check we do. + if commitType == lnwallet.CommitmentTypeAnchors { + timeoutFee := lnwallet.HtlcTimeoutFee( + channeldb.AnchorOutputsBit, + f.cfg.MaxAnchorsCommitFeeRate, + ) + + maxNumHtlcs := chanReserve / timeoutFee + if maxNumHtlcs < lnwallet.MinAnchorHtlcSlots { + if err := reservation.Cancel(); err != nil { + fndgLog.Errorf("unable to cancel "+ + "reservation: %v", err) + } + + maxHtlcErr := fmt.Errorf("tiny channel, only room "+ + "for %d HTLCs. Consider making a larger "+ + "channel or reducing the max commit fee rate", + maxNumHtlcs) + msg.err <- maxHtlcErr + return + } + } + fndgLog.Infof("Target commit tx sat/kw for pendingID(%x): %v", chanID, int64(commitFeePerKw)) @@ -3279,11 +3322,6 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { // request to the remote peer, kicking off the funding workflow. ourContribution := reservation.OurContribution() - // Finally, we'll use the current value of the channels and our default - // policy to determine of required commitment constraints for the - // remote party. - chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit) - fndgLog.Infof("Starting funding workflow with %v for pending_id(%x), "+ "committype=%v", msg.peer.Address(), chanID, commitType) diff --git a/htlcswitch/link.go b/htlcswitch/link.go index 6a7c31284b..b5539b9f0b 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -269,6 +269,10 @@ type ChannelLinkConfig struct { // initiator of the channel. MaxFeeAllocation float64 + // MaxAnchorsCommitFeeRate is the max commitment fee rate we'll use as + // the initiator for channels of the anchor type. + MaxAnchorsCommitFeeRate chainfee.SatPerKWeight + // NotifyActiveLink allows the link to tell the ChannelNotifier when a // link is first started. NotifyActiveLink func(wire.OutPoint) @@ -1083,7 +1087,10 @@ func (l *channelLink) htlcManager() { // based on our current set fee rate. We'll cap the new // fee rate to our max fee allocation. commitFee := l.channel.CommitFeeRate() - maxFee := l.channel.MaxFeeRate(l.cfg.MaxFeeAllocation) + maxFee := l.channel.MaxFeeRate( + l.cfg.MaxFeeAllocation, + l.cfg.MaxAnchorsCommitFeeRate, + ) newCommitFee := chainfee.SatPerKWeight( math.Min(float64(netFee), float64(maxFee)), ) diff --git a/htlcswitch/test_utils.go b/htlcswitch/test_utils.go index 37925ef9f0..c4fa61b8aa 100644 --- a/htlcswitch/test_utils.go +++ b/htlcswitch/test_utils.go @@ -1185,6 +1185,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer, OutgoingCltvRejectDelta: 3, MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry, MaxFeeAllocation: DefaultMaxLinkFeeAllocation, + MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(10 * 1000).FeePerKWeight(), NotifyActiveLink: func(wire.OutPoint) {}, NotifyActiveChannel: func(wire.OutPoint) {}, NotifyInactiveChannel: func(wire.OutPoint) {}, diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 090f8f00da..607ab67abe 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -61,6 +61,14 @@ var ( ErrBelowChanReserve = fmt.Errorf("commitment transaction dips peer " + "below chan reserve") + // ErrHTLCTimeoutFeesTooLarge is returned when a proposed combination + // of added HTLCs and fee rate would create a state where the fees for + // 2nd level HTLC timeout transactions are greater than the peers's + // channel reserve. We disallow this state since it could be in the + // peer's incentive to breach using this state. + ErrHTLCTimeoutFeesTooLarge = fmt.Errorf("total HTLC timeout fees " + + "greater than reserve") + // ErrBelowMinHTLC is returned when a proposed HTLC has a value that // is below the minimum HTLC value constraint for either us or our // peer depending on which flags are set. @@ -1299,6 +1307,11 @@ type LightningChannel struct { // log is a channel-specific logging instance. log btclog.Logger + // skipFeeSiphonCheck will make us skip the fee siphon check in + // validateCommitmentSanity. This _should only be set true for certain + // tests_. + skipFeeSiphonCheck bool + sync.RWMutex } @@ -2583,12 +2596,15 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, // these balances will be *before* taking a commitment fee from the // initiator. htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex) - ourBalance, theirBalance, _, filteredHTLCView, err := lc.computeView( + computedView, err := lc.computeView( htlcView, remoteChain, true, ) if err != nil { return nil, err } + filteredHTLCView := computedView.filteredView + ourBalance := computedView.ourBalance + theirBalance := computedView.theirBalance feePerKw := filteredHTLCView.feePerKw // Actually generate unsigned commitment transaction for this view. @@ -3372,14 +3388,34 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter, ourInitialBalance := commitChain.tip().ourBalance theirInitialBalance := commitChain.tip().theirBalance - ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView( - view, remoteChain, false, - ) + computedView, err := lc.computeView(view, remoteChain, false) if err != nil { return err } + ourBalance := computedView.ourBalance + theirBalance := computedView.theirBalance + commitWeight := computedView.commitWeight + filteredView := computedView.filteredView feePerKw := filteredView.feePerKw + // For the anchor channel type, we want to disallow states where the + // potential fees siphoned from HTLC timeout transactions are greater + // than what is owed to the node in any future state. Since we know + // that they must always keep the reserve available for punishment, we + // disallow any state where this wouldn't be the case. + reserve := lc.channelState.LocalChanCfg.ChanReserve + if remoteChain { + reserve = lc.channelState.RemoteChanCfg.ChanReserve + } + + // TODO(halseth): this limits the number of HTLCs that can be used + // together with small channel reserves. Should move to a channel type + // where the committed fees for HTLC transactions are zero (only BYOF) + // or zero for both commitment+HTLCs (needs package relay). + if !lc.skipFeeSiphonCheck && computedView.feeSiphon > reserve { + return ErrHTLCTimeoutFeesTooLarge + } + // Calculate the commitment fee, and subtract it from the initiator's // balance. commitFee := feePerKw.FeeForWeight(commitWeight) @@ -3960,6 +3996,33 @@ func (lc *LightningChannel) ProcessChanSyncMsg( return updates, openedCircuits, closedCircuits, nil } +// computedHtlcView holds information for an evaluated HTLC view where we +// computed the final balances and commitment, after applying the HTLCs to the +// latest commmitment. +// +// NOTE: The balances are the balances *before* subtracting the commitment fee +// from the initiator's balance. +type computedHtlcView struct { + // ourBalance is the balance of the local node (us) after evaluating + // this view. + ourBalance lnwire.MilliSatoshi + + // theirBalance is the balance of the remote node (them) after + // evaluating this view. + theirBalance lnwire.MilliSatoshi + + // commitWeight is the weight of the commitment. + commitWeight int64 + + // feeSiphon is the total HTLC timeout fees, that can potentially be + // siphoned off in case this is a anchor type channel. + feeSiphon btcutil.Amount + + // filteredView is the evaluated HTLC view where we have removed any + // settled or failed HTLC from the view. + filteredView *htlcView +} + // computeView takes the given htlcView, and calculates the balances, filtered // view (settling unsettled HTLCs), commitment weight and feePerKw, after // applying the HTLCs to the latest commitment. The returned balances are the @@ -3969,8 +4032,7 @@ func (lc *LightningChannel) ProcessChanSyncMsg( // If the updateState boolean is set true, the add and remove heights of the // HTLCs will be set to the next commitment height. func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, - updateState bool) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, int64, - *htlcView, error) { + updateState bool) (*computedHtlcView, error) { commitChain := lc.localCommitChain dustLimit := lc.channelState.LocalChanCfg.DustLimit @@ -4012,13 +4074,21 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, filteredHTLCView, err := lc.evaluateHTLCView(view, &ourBalance, &theirBalance, nextHeight, remoteChain, updateState) if err != nil { - return 0, 0, 0, nil, err + return nil, err } feePerKw := filteredHTLCView.feePerKw // Now go through all HTLCs at this stage, to calculate the total // weight, needed to calculate the transaction fee. var totalHtlcWeight int64 + + // For non-dust HTLCs offered for anchor channels, the offerer can + // potentially avoid retribution by publishing a revoked state and + // siphon fees off the HTLC timeout transactions. We want to disallow + // these states, so we count how much fees are available to do this. + var htlcTimeoutFees btcutil.Amount + anchors := lc.channelState.ChanType.HasAnchors() + for _, htlc := range filteredHTLCView.ourUpdates { if htlcIsDust( lc.channelState.ChanType, false, !remoteChain, @@ -4028,7 +4098,17 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, } totalHtlcWeight += input.HTLCWeight + + // For local commitments, we don't want to allow signing states + // that violate the fee siphoning invariant. + if anchors && !remoteChain { + htlcFee := HtlcTimeoutFee( + lc.channelState.ChanType, feePerKw, + ) + htlcTimeoutFees += htlcFee + } } + for _, htlc := range filteredHTLCView.theirUpdates { if htlcIsDust( lc.channelState.ChanType, true, !remoteChain, @@ -4038,11 +4118,25 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool, } totalHtlcWeight += input.HTLCWeight + + // Calculate potential siphoned fees by the remote. + if anchors && remoteChain { + htlcFee := HtlcTimeoutFee( + lc.channelState.ChanType, feePerKw, + ) + htlcTimeoutFees += htlcFee + } } totalCommitWeight := CommitWeight(lc.channelState.ChanType) + totalHtlcWeight - return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView, nil + return &computedHtlcView{ + ourBalance: ourBalance, + theirBalance: theirBalance, + commitWeight: totalCommitWeight, + feeSiphon: htlcTimeoutFees, + filteredView: filteredHTLCView, + }, nil } // genHtlcSigValidationJobs generates a series of signatures verification jobs @@ -6490,13 +6584,16 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView, // Compute the current balances for this commitment. This will take // into account HTLCs to determine the commit weight, which the // initiator must pay the fee for. - ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView( - view, remoteChain, false, - ) + computedView, err := lc.computeView(view, remoteChain, false) if err != nil { lc.log.Errorf("Unable to fetch available balance: %v", err) return 0, 0 } + ourBalance := computedView.ourBalance + theirBalance := computedView.theirBalance + filteredView := computedView.filteredView + commitWeight := computedView.commitWeight + feeSiphon := computedView.feeSiphon // We can never spend from the channel reserve, so we'll subtract it // from our available balance. @@ -6517,6 +6614,19 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView, feePerKw.FeeForWeight(commitWeight + input.HTLCWeight), ) + // We want to make sure we won't try to add an HTLC that will violate + // the fee siphoning invariant. So if we are adding an HTLC on our + // commitment, the HTLC timeout fee shouldn't take the total fees that + // can be siphoned above our reserve. + if lc.channelState.ChanType.HasAnchors() && !remoteChain { + htlcTimeoutFee := HtlcTimeoutFee( + lc.channelState.ChanType, feePerKw, + ) + if feeSiphon+htlcTimeoutFee > lc.channelState.LocalChanCfg.ChanReserve { + return 0, commitWeight + } + } + // If we are the channel initiator, we must to subtract this commitment // fee from our available balance in order to ensure we can afford both // the value of the HTLC and the additional commitment fee from adding @@ -6665,6 +6775,24 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error { EntryType: FeeUpdate, } + // Make sure updating the fee won't violate any of the constraints we + // must keep on the commitment transactions. + remoteACKedIndex := lc.localCommitChain.tail().theirMessageIndex + err := lc.validateCommitmentSanity( + remoteACKedIndex, lc.localUpdateLog.logIndex, true, pd, nil, + ) + if err != nil { + return err + } + + err = lc.validateCommitmentSanity( + lc.remoteUpdateLog.logIndex, lc.localUpdateLog.logIndex, + false, pd, nil, + ) + if err != nil { + return err + } + lc.localUpdateLog.appendUpdate(pd) return nil @@ -6779,11 +6907,14 @@ 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. This can be useful to determine when we should -// stop proposing fee updates that exceed our maximum allocation. +// stop proposing fee updates that exceed our maximum allocation. We also take +// a fee rate cap that should be used for anchor type channels. // // 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, + maxAnchorFeeRate chainfee.SatPerKWeight) chainfee.SatPerKWeight { + lc.RLock() defer lc.RUnlock() @@ -6798,9 +6929,16 @@ func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWe // Ensure the fee rate doesn't dip below the fee floor. _, weight := lc.availableBalance() maxFeeRate := maxFee / (float64(weight) / 1000) - return chainfee.SatPerKWeight( + feeRate := chainfee.SatPerKWeight( math.Max(maxFeeRate, float64(chainfee.FeePerKwFloor)), ) + + // Cap anchor fee rates. + if lc.channelState.ChanType.HasAnchors() && feeRate > maxAnchorFeeRate { + return maxAnchorFeeRate + } + + return feeRate } // RemoteNextRevocation returns the channelState's RemoteNextRevocation. diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index b69b2faff2..ead91598bc 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -7996,6 +7996,19 @@ func TestForceCloseBorkedState(t *testing.T) { func TestChannelMaxFeeRate(t *testing.T) { t.Parallel() + assertMaxFeeRate := func(c *LightningChannel, + maxAlloc float64, anchorMax, expFeeRate chainfee.SatPerKWeight) { + + t.Helper() + + maxFeeRate := c.MaxFeeRate(maxAlloc, anchorMax) + if maxFeeRate != expFeeRate { + t.Fatalf("expected max fee rate of %v with max "+ + "allocation of %v, got %v", expFeeRate, + maxAlloc, maxFeeRate) + } + } + aliceChannel, _, cleanUp, err := CreateTestChannels( channeldb.SingleFunderTweaklessBit, ) @@ -8004,21 +8017,32 @@ func TestChannelMaxFeeRate(t *testing.T) { } defer cleanUp() - assertMaxFeeRate := func(maxAlloc float64, - expFeeRate chainfee.SatPerKWeight) { + assertMaxFeeRate(aliceChannel, 1.0, 0, 690607734) + assertMaxFeeRate(aliceChannel, 0.001, 0, 690607) + assertMaxFeeRate(aliceChannel, 0.000001, 0, 690) + assertMaxFeeRate(aliceChannel, 0.0000001, 0, chainfee.FeePerKwFloor) - maxFeeRate := aliceChannel.MaxFeeRate(maxAlloc) - if maxFeeRate != expFeeRate { - t.Fatalf("expected max fee rate of %v with max "+ - "allocation of %v, got %v", expFeeRate, - maxAlloc, maxFeeRate) - } + // Check that anchor channels are capped at their max fee rate. + anchorChannel, _, cleanUp, err := CreateTestChannels( + channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit, + ) + if err != nil { + t.Fatalf("unable to create test channels: %v", err) } + defer cleanUp() - assertMaxFeeRate(1.0, 690607734) - assertMaxFeeRate(0.001, 690607) - assertMaxFeeRate(0.000001, 690) - assertMaxFeeRate(0.0000001, chainfee.FeePerKwFloor) + // 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, 444839) + assertMaxFeeRate(anchorChannel, 0.001, 300000, 300000) + assertMaxFeeRate(anchorChannel, 0.000001, 700, 444) + assertMaxFeeRate( + anchorChannel, 0.0000001, 1000000, chainfee.FeePerKwFloor, + ) } // TestChannelFeeRateFloor asserts that valid commitments can be proposed and diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 44b3c81c48..8bd27d5780 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -14,8 +14,38 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) -// anchorSize is the constant anchor output size. -const anchorSize = btcutil.Amount(330) +const ( + // anchorSize is the constant anchor output size. + anchorSize = btcutil.Amount(330) + + // DefaultAnchorsCommitMaxFeeRateSatPerVByte is the default max fee + // rate in sat/vbyte the initiator will use for anchor channels. + // + // This caps the update fee the initiator will send when the anchors + // channel type is used. We do not limit anything on the receiver side, + // only on the sender (initiator) side. This keeps us spec compatible, + // while defaulting to a reasonable feerate if initiator is lnd. + // Receiver side we will allow any fee and add updates as long as the + // fee siphon invariant is not violated. + // + // Defaults to 10 sat/vbyte. This will make give HTLC a timeout fee of + // + // 666 * 2500 / 1000 = 1665 sats + // + // An example: + // For a channel size of 0.01 BTC = 1,000,000 sats and a channel + // reserve of 1%, this leaves room for (1,000,000 / 100) / 1665 = 6 + // HTLCs. + // + // Bigger channels, larger reserves, or lower fee rates will open up + // for more room. + DefaultAnchorsCommitMaxFeeRateSatPerVByte = 10 + + // MinAnchorHtlcSlots is the minimum number of slots we require to be + // available for HTLCs when opening an anchor channels and checking fee + // leaks. See doc for DefaultAnchorsCommitMaxFeeRateSatPerVByte + MinAnchorHtlcSlots = 6 +) // CommitmentKeyRing holds all derived keys needed to construct commitment and // HTLC transactions. The keys are derived differently depending whether the diff --git a/lnwallet/transactions_test.go b/lnwallet/transactions_test.go index 696328cdb2..8f001beaa9 100644 --- a/lnwallet/transactions_test.go +++ b/lnwallet/transactions_test.go @@ -277,6 +277,11 @@ func testVectors(t *testing.T, chanType channeldb.ChannelType, test testCase) { ) defer cleanUp() + // Since these test vectors create states where our fee siphon check + // would fail, we skip it. + remoteChannel.skipFeeSiphonCheck = true + localChannel.skipFeeSiphonCheck = true + // Add htlcs (if any) to the update logs of both sides and save a hash // map that allows us to identify the htlcs in the scripts later on and // retrieve the corresponding preimage. diff --git a/peer/brontide.go b/peer/brontide.go index 578d713379..7428f67916 100644 --- a/peer/brontide.go +++ b/peer/brontide.go @@ -294,6 +294,10 @@ type Config struct { // commitment fee. This only applies for the initiator of the channel. MaxChannelFeeAllocation float64 + // MaxAnchorsCommitFeeRate is the maximum fee rate we'll use as an + // initiator for anchor channel commitments. + MaxAnchorsCommitFeeRate chainfee.SatPerKWeight + // ServerPubKey is the serialized, compressed public key of our lnd node. // It is used to determine which policy (channel edge) to pass to the // ChannelLink. @@ -801,6 +805,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint, TowerClient: towerClient, MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry, MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation, + MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate, NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent, NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent, NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent, diff --git a/server.go b/server.go index 7b440ec0eb..fa9d811d3a 100644 --- a/server.go +++ b/server.go @@ -3119,7 +3119,9 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq, UnsafeReplay: s.cfg.UnsafeReplay, MaxOutgoingCltvExpiry: s.cfg.MaxOutgoingCltvExpiry, MaxChannelFeeAllocation: s.cfg.MaxChannelFeeAllocation, - Quit: s.quit, + MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte( + s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(), + Quit: s.quit, } copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed())