@@ -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"
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.
78138type 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.
286353func (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