Skip to content

Commit e4b5aa9

Browse files
authored
Merge pull request #6770 from Roasbeef/co-op-close-with-more-spec-innit
lnwallet/chancloser: remove the commit fee clamp, introduce max fee
2 parents ec7be96 + 046aa7f commit e4b5aa9

File tree

11 files changed

+2313
-2015
lines changed

11 files changed

+2313
-2015
lines changed

docs/release-notes/release-notes-0.15.1.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Release Notes
22

3-
## Protocol Extensions
3+
## Protocol/Spec Updates
4+
5+
* [We'll now no longer clamp the co-op close fee to the commitment
6+
fee](https://github.com/lightningnetwork/lnd/pull/6770). Instead, if users are
7+
the initiator, they can now specify a max fee that should be respected.
48

59
### Zero-Conf Channel Opens
610
* [Introduces support for zero-conf channel opens and non-zero-conf option_scid_alias channels.](https://github.com/lightningnetwork/lnd/pull/5955)

htlcswitch/switch.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ type ChanClose struct {
106106
// process for the cooperative closure transaction kicks off.
107107
TargetFeePerKw chainfee.SatPerKWeight
108108

109+
// MaxFee is the highest fee the caller is willing to pay.
110+
//
111+
// NOTE: This field is only respected if the caller is the initiator of
112+
// the channel.
113+
MaxFee chainfee.SatPerKWeight
114+
109115
// DeliveryScript is an optional delivery script to pay funds out to.
110116
DeliveryScript lnwire.DeliveryAddress
111117

@@ -1656,7 +1662,7 @@ func (s *Switch) teardownCircuit(pkt *htlcPacket) error {
16561662
// optional parameter which sets a user specified script to close out to.
16571663
func (s *Switch) CloseLink(chanPoint *wire.OutPoint,
16581664
closeType contractcourt.ChannelCloseType,
1659-
targetFeePerKw chainfee.SatPerKWeight,
1665+
targetFeePerKw, maxFee chainfee.SatPerKWeight,
16601666
deliveryScript lnwire.DeliveryAddress) (chan interface{}, chan error) {
16611667

16621668
// TODO(roasbeef) abstract out the close updates.
@@ -1668,6 +1674,7 @@ func (s *Switch) CloseLink(chanPoint *wire.OutPoint,
16681674
ChanPoint: chanPoint,
16691675
Updates: updateChan,
16701676
TargetFeePerKw: targetFeePerKw,
1677+
MaxFee: maxFee,
16711678
DeliveryScript: deliveryScript,
16721679
Err: errChan,
16731680
}

lnrpc/lightning.pb.go

Lines changed: 2002 additions & 1988 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lnrpc/lightning.proto

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1910,6 +1910,11 @@ message CloseChannelRequest {
19101910
// A manual fee rate set in sat/vbyte that should be used when crafting the
19111911
// closure transaction.
19121912
uint64 sat_per_vbyte = 6;
1913+
1914+
// The maximum fee rate the closer is willing to pay.
1915+
//
1916+
// NOTE: This field is only respected if we're the initiator of the channel.
1917+
uint64 max_fee_per_vbyte = 7;
19131918
}
19141919

19151920
message CloseStatusUpdate {

lnrpc/lightning.swagger.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,14 @@
811811
"required": false,
812812
"type": "string",
813813
"format": "uint64"
814+
},
815+
{
816+
"name": "max_fee_per_vbyte",
817+
"description": "The maximum fee rate the closer is willing to pay.\n\nNOTE: This field is only respected if we're the initiator of the channel.",
818+
"in": "query",
819+
"required": false,
820+
"type": "string",
821+
"format": "uint64"
814822
}
815823
],
816824
"tags": [

lnwallet/chancloser/chancloser.go

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ import (
66

77
"github.com/btcsuite/btcd/btcutil"
88
"github.com/btcsuite/btcd/chaincfg"
9+
"github.com/btcsuite/btcd/chaincfg/chainhash"
910
"github.com/btcsuite/btcd/txscript"
1011
"github.com/btcsuite/btcd/wire"
1112
"github.com/davecgh/go-spew/spew"
1213
"github.com/lightningnetwork/lnd/htlcswitch"
14+
"github.com/lightningnetwork/lnd/input"
1315
"github.com/lightningnetwork/lnd/labels"
1416
"github.com/lightningnetwork/lnd/lnwallet"
1517
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -35,6 +37,12 @@ var (
3537
// shutdown script previously set for that party.
3638
ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown script does not " +
3739
"match upfront shutdown script")
40+
41+
// ErrProposalExeceedsMaxFee is returned when as the initiator, the
42+
// latest fee proposal sent by the responder exceed our max fee.
43+
// responder.
44+
ErrProposalExeceedsMaxFee = fmt.Errorf("latest fee proposal exceeds " +
45+
"max fee")
3846
)
3947

4048
// closeState represents all the possible states the channel closer state
@@ -73,11 +81,63 @@ const (
7381
closeFinished
7482
)
7583

84+
const (
85+
// defaultMaxFeeMultiplier is a multiplier we'll apply to the ideal fee
86+
// of the initiator, to decide when the negotiated fee is too high. By
87+
// default, we want to bail out if we attempt to negotiate a fee that's
88+
// 3x higher than our max fee.
89+
defaultMaxFeeMultiplier = 3
90+
)
91+
92+
// Channel abstracts away from the core channel state machine by exposing an
93+
// interface that requires only the methods we need to carry out the channel
94+
// closing process.
95+
type Channel interface {
96+
// CalcFee returns the absolute fee for the given fee rate.
97+
CalcFee(chainfee.SatPerKWeight) btcutil.Amount
98+
99+
// ChannelPoint returns the channel point of the target channel.
100+
ChannelPoint() *wire.OutPoint
101+
102+
// MarkCoopBroadcasted persistently marks that the channel close
103+
// transaction has been broadcast.
104+
MarkCoopBroadcasted(*wire.MsgTx, bool) error
105+
106+
// IsInitiator returns true we are the initiator of the channel.
107+
IsInitiator() bool
108+
109+
// ShortChanID returns the scid of the channel.
110+
ShortChanID() lnwire.ShortChannelID
111+
112+
// AbsoluteThawHeight returns the absolute thaw height of the channel.
113+
// If the channel is pending, or an unconfirmed zero conf channel, then
114+
// an error should be returned.
115+
AbsoluteThawHeight() (uint32, error)
116+
117+
// RemoteUpfrontShutdownScript returns the upfront shutdown script of
118+
// the remote party. If the remote party didn't specify such a script,
119+
// an empty delivery address should be returned.
120+
RemoteUpfrontShutdownScript() lnwire.DeliveryAddress
121+
122+
// CreateCloseProposal creates a new co-op close proposal in the form
123+
// of a valid signature, the chainhash of the final txid, and our final
124+
// balance in the created state.
125+
CreateCloseProposal(proposedFee btcutil.Amount, localDeliveryScript []byte,
126+
remoteDeliveryScript []byte) (input.Signature, *chainhash.Hash,
127+
btcutil.Amount, error)
128+
129+
// CompleteCooperativeClose persistently "completes" the cooperative
130+
// close by producing a fully signed co-op close transaction.
131+
CompleteCooperativeClose(localSig, remoteSig input.Signature,
132+
localDeliveryScript, remoteDeliveryScript []byte,
133+
proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error)
134+
}
135+
76136
// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
77137
// duties.
78138
type ChanCloseCfg struct {
79139
// Channel is the channel that should be closed.
80-
Channel *lnwallet.LightningChannel
140+
Channel Channel
81141

82142
// BroadcastTx broadcasts the passed transaction to the network.
83143
BroadcastTx func(*wire.MsgTx, string) error
@@ -89,6 +149,10 @@ type ChanCloseCfg struct {
89149
// Disconnect will disconnect from the remote peer in this close.
90150
Disconnect func() error
91151

152+
// MaxFee, is non-zero represents the highest fee that the initiator is
153+
// willing to pay to close the channel.
154+
MaxFee chainfee.SatPerKWeight
155+
92156
// Quit is a channel that should be sent upon in the occasion the state
93157
// machine should cease all progress and shutdown.
94158
Quit chan struct{}
@@ -122,6 +186,11 @@ type ChanCloser struct {
122186
// offer when starting negotiation. This will be used as a baseline.
123187
idealFeeSat btcutil.Amount
124188

189+
// maxFee is the highest fee the initiator is willing to pay to close
190+
// out the channel. This is either a use specified value, or a default
191+
// multiplier based of the initial starting ideal fee.
192+
maxFee btcutil.Amount
193+
125194
// lastFeeProposal is the last fee that we proposed to the remote party.
126195
// We'll use this as a pivot point to ratchet our next offer up, down, or
127196
// simply accept the remote party's prior offer.
@@ -162,23 +231,16 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
162231
idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
163232
closeReq *htlcswitch.ChanClose, locallyInitiated bool) *ChanCloser {
164233

165-
// Given the target fee-per-kw, we'll compute what our ideal _total_ fee
166-
// will be starting at for this fee negotiation.
167-
//
168-
// TODO(roasbeef): should factor in minimal commit
234+
// Given the target fee-per-kw, we'll compute what our ideal _total_
235+
// fee will be starting at for this fee negotiation.
169236
idealFeeSat := cfg.Channel.CalcFee(idealFeePerKw)
170237

171-
// If this fee is greater than the fee currently present within the
172-
// commitment transaction, then we'll clamp it down to be within the proper
173-
// range.
174-
//
175-
// TODO(roasbeef): clamp fee func?
176-
channelCommitFee := cfg.Channel.StateSnapshot().CommitFee
177-
if idealFeeSat > channelCommitFee {
178-
chancloserLog.Infof("Ideal starting fee of %v is greater than commit "+
179-
"fee of %v, clamping", int64(idealFeeSat), int64(channelCommitFee))
180-
181-
idealFeeSat = channelCommitFee
238+
// When we're the initiator, we'll want to also factor in the highest
239+
// fee we want to pay. This'll either be 3x the ideal fee, or the
240+
// specified explicit max fee.
241+
maxFee := idealFeeSat * defaultMaxFeeMultiplier
242+
if cfg.MaxFee > 0 {
243+
maxFee = cfg.Channel.CalcFee(cfg.MaxFee)
182244
}
183245

184246
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) is: %v sat",
@@ -193,6 +255,7 @@ func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
193255
cfg: cfg,
194256
negotiationHeight: negotiationHeight,
195257
idealFeeSat: idealFeeSat,
258+
maxFee: maxFee,
196259
localDeliveryScript: deliveryScript,
197260
priorFeeOffers: make(map[btcutil.Amount]*lnwire.ClosingSigned),
198261
locallyInitiated: locallyInitiated,
@@ -282,9 +345,13 @@ func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
282345
return c.closeReq
283346
}
284347

285-
// Channel returns the channel stored in the config.
348+
// Channel returns the channel stored in the config as a
349+
// *lnwallet.LightningChannel.
350+
//
351+
// NOTE: This method will PANIC if the underlying channel implementation isn't
352+
// the desired type.
286353
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
287-
return c.cfg.Channel
354+
return c.cfg.Channel.(*lnwallet.LightningChannel)
288355
}
289356

290357
// NegotiationHeight returns the negotiation height.
@@ -352,9 +419,8 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
352419
// initiator of the channel opening, then we'll deny their close
353420
// attempt.
354421
chanInitiator := c.cfg.Channel.IsInitiator()
355-
chanState := c.cfg.Channel.State()
356422
if !chanInitiator {
357-
absoluteThawHeight, err := chanState.AbsoluteThawHeight()
423+
absoluteThawHeight, err := c.cfg.Channel.AbsoluteThawHeight()
358424
if err != nil {
359425
return nil, false, err
360426
}
@@ -483,9 +549,10 @@ func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
483549
feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat,
484550
c.lastFeeProposal, remoteProposedFee,
485551
)
486-
if feeProposal > c.idealFeeSat*3 {
487-
return nil, false, fmt.Errorf("couldn't find" +
488-
" compromise fee")
552+
if c.cfg.Channel.IsInitiator() && feeProposal > c.maxFee {
553+
return nil, false, fmt.Errorf("%w: %v > %v",
554+
ErrProposalExeceedsMaxFee, feeProposal,
555+
c.maxFee)
489556
}
490557

491558
// With our new fee proposal calculated, we'll craft a new close

0 commit comments

Comments
 (0)