• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 12375116696

17 Dec 2024 02:29PM UTC coverage: 58.366% (-0.2%) from 58.595%
12375116696

Pull #8777

github

ziggie1984
docs: add release-notes
Pull Request #8777: multi: make deletion of edge atomic.

132 of 177 new or added lines in 6 files covered. (74.58%)

670 existing lines in 37 files now uncovered.

133926 of 229458 relevant lines covered (58.37%)

19223.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.52
/lnwallet/chancloser/chancloser.go
1
package chancloser
2

3
import (
4
        "bytes"
5
        "fmt"
6

7
        "github.com/btcsuite/btcd/btcec/v2"
8
        "github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
9
        "github.com/btcsuite/btcd/btcutil"
10
        "github.com/btcsuite/btcd/chaincfg"
11
        "github.com/btcsuite/btcd/txscript"
12
        "github.com/btcsuite/btcd/wire"
13
        "github.com/davecgh/go-spew/spew"
14
        "github.com/lightningnetwork/lnd/channeldb"
15
        "github.com/lightningnetwork/lnd/fn/v2"
16
        "github.com/lightningnetwork/lnd/htlcswitch"
17
        "github.com/lightningnetwork/lnd/input"
18
        "github.com/lightningnetwork/lnd/labels"
19
        "github.com/lightningnetwork/lnd/lntypes"
20
        "github.com/lightningnetwork/lnd/lnutils"
21
        "github.com/lightningnetwork/lnd/lnwallet"
22
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
23
        "github.com/lightningnetwork/lnd/lnwire"
24
)
25

26
var (
27
        // ErrChanAlreadyClosing is returned when a channel shutdown is
28
        // attempted more than once.
29
        ErrChanAlreadyClosing = fmt.Errorf("channel shutdown already initiated")
30

31
        // ErrChanCloseNotFinished is returned when a caller attempts to access
32
        // a field or function that is contingent on the channel closure
33
        // negotiation already being completed.
34
        ErrChanCloseNotFinished = fmt.Errorf("close negotiation not finished")
35

36
        // ErrInvalidState is returned when the closing state machine receives a
37
        // message while it is in an unknown state.
38
        ErrInvalidState = fmt.Errorf("invalid state")
39

40
        // ErrUpfrontShutdownScriptMismatch is returned when a peer or end user
41
        // provides a cooperative close script which does not match the upfront
42
        // shutdown script previously set for that party.
43
        ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown script does not " +
44
                "match upfront shutdown script")
45

46
        // ErrProposalExceedsMaxFee is returned when as the initiator, the
47
        // latest fee proposal sent by the responder exceed our max fee.
48
        // responder.
49
        ErrProposalExceedsMaxFee = fmt.Errorf("latest fee proposal exceeds " +
50
                "max fee")
51

52
        // ErrInvalidShutdownScript is returned when we receive an address from
53
        // a peer that isn't either a p2wsh or p2tr address.
54
        ErrInvalidShutdownScript = fmt.Errorf("invalid shutdown script")
55

56
        // errNoShutdownNonce is returned when a shutdown message is received
57
        // w/o a nonce for a taproot channel.
58
        errNoShutdownNonce = fmt.Errorf("shutdown nonce not populated")
59
)
60

61
// closeState represents all the possible states the channel closer state
62
// machine can be in. Each message will either advance to the next state, or
63
// remain at the current state. Once the state machine reaches a state of
64
// closeFinished, then negotiation is over.
65
type closeState uint8
66

67
const (
68
        // closeIdle is the initial starting state. In this state, the state
69
        // machine has been instantiated, but no state transitions have been
70
        // attempted. If a state machine receives a message while in this state,
71
        // then it is the responder to an initiated cooperative channel closure.
72
        closeIdle closeState = iota
73

74
        // closeShutdownInitiated is the state that's transitioned to once the
75
        // initiator of a closing workflow sends the shutdown message. At this
76
        // point, they're waiting for the remote party to respond with their own
77
        // shutdown message. After which, they'll both enter the fee negotiation
78
        // phase.
79
        closeShutdownInitiated
80

81
        // closeAwaitingFlush is the state that's transitioned to once both
82
        // Shutdown messages have been exchanged but we are waiting for the
83
        // HTLCs to clear out of the channel.
84
        closeAwaitingFlush
85

86
        // closeFeeNegotiation is the third, and most persistent state. Both
87
        // parties enter this state after they've sent and received a shutdown
88
        // message. During this phase, both sides will send monotonically
89
        // increasing fee requests until one side accepts the last fee rate
90
        // offered by the other party. In this case, the party will broadcast
91
        // the closing transaction, and send the accepted fee to the remote
92
        // party. This then causes a shift into the closeFinished state.
93
        closeFeeNegotiation
94

95
        // closeFinished is the final state of the state machine. In this state,
96
        // a side has accepted a fee offer and has broadcast the valid closing
97
        // transaction to the network. During this phase, the closing
98
        // transaction becomes available for examination.
99
        closeFinished
100
)
101

102
const (
103
        // defaultMaxFeeMultiplier is a multiplier we'll apply to the ideal fee
104
        // of the initiator, to decide when the negotiated fee is too high. By
105
        // default, we want to bail out if we attempt to negotiate a fee that's
106
        // 3x higher than our max fee.
107
        defaultMaxFeeMultiplier = 3
108
)
109

110
// DeliveryAddrWithKey wraps a normal delivery addr, but also includes the
111
// internal key for the delivery addr if known.
112
type DeliveryAddrWithKey struct {
113
        // DeliveryAddress is the raw, serialized pkScript of the delivery
114
        // address.
115
        lnwire.DeliveryAddress
116

117
        // InternalKey is the Taproot internal key of the delivery address, if
118
        // the address is a P2TR output.
119
        InternalKey fn.Option[btcec.PublicKey]
120
}
121

122
// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
123
// duties.
124
type ChanCloseCfg struct {
125
        // Channel is the channel that should be closed.
126
        Channel Channel
127

128
        // MusigSession is used to handle generating musig2 nonces, and also
129
        // creating the proper set of closing options for taproot channels.
130
        MusigSession MusigSession
131

132
        // BroadcastTx broadcasts the passed transaction to the network.
133
        BroadcastTx func(*wire.MsgTx, string) error
134

135
        // DisableChannel disables a channel, resulting in it not being able to
136
        // forward payments.
137
        DisableChannel func(wire.OutPoint) error
138

139
        // Disconnect will disconnect from the remote peer in this close.
140
        Disconnect func() error
141

142
        // MaxFee, is non-zero represents the highest fee that the initiator is
143
        // willing to pay to close the channel.
144
        MaxFee chainfee.SatPerKWeight
145

146
        // ChainParams holds the parameters of the chain that we're active on.
147
        ChainParams *chaincfg.Params
148

149
        // Quit is a channel that should be sent upon in the occasion the state
150
        // machine should cease all progress and shutdown.
151
        Quit chan struct{}
152

153
        // FeeEstimator is used to estimate the absolute starting co-op close
154
        // fee.
155
        FeeEstimator CoopFeeEstimator
156

157
        // AuxCloser is an optional interface that can be used to modify the
158
        // way the co-op close process proceeds.
159
        AuxCloser fn.Option[AuxChanCloser]
160
}
161

162
// ChanCloser is a state machine that handles the cooperative channel closure
163
// procedure. This includes shutting down a channel, marking it ineligible for
164
// routing HTLC's, negotiating fees with the remote party, and finally
165
// broadcasting the fully signed closure transaction to the network.
166
type ChanCloser struct {
167
        // state is the current state of the state machine.
168
        state closeState
169

170
        // cfg holds the configuration for this ChanCloser instance.
171
        cfg ChanCloseCfg
172

173
        // chanPoint is the full channel point of the target channel.
174
        chanPoint wire.OutPoint
175

176
        // cid is the full channel ID of the target channel.
177
        cid lnwire.ChannelID
178

179
        // negotiationHeight is the height that the fee negotiation begun at.
180
        negotiationHeight uint32
181

182
        // closingTx is the final, fully signed closing transaction. This will
183
        // only be populated once the state machine shifts to the closeFinished
184
        // state.
185
        closingTx *wire.MsgTx
186

187
        // idealFeeSat is the ideal fee that the state machine should initially
188
        // offer when starting negotiation. This will be used as a baseline.
189
        idealFeeSat btcutil.Amount
190

191
        // maxFee is the highest fee the initiator is willing to pay to close
192
        // out the channel. This is either a use specified value, or a default
193
        // multiplier based of the initial starting ideal fee.
194
        maxFee btcutil.Amount
195

196
        // idealFeeRate is our ideal fee rate.
197
        idealFeeRate chainfee.SatPerKWeight
198

199
        // lastFeeProposal is the last fee that we proposed to the remote party.
200
        // We'll use this as a pivot point to ratchet our next offer up, down,
201
        // or simply accept the remote party's prior offer.
202
        lastFeeProposal btcutil.Amount
203

204
        // priorFeeOffers is a map that keeps track of all the proposed fees
205
        // that we've offered during the fee negotiation. We use this map to cut
206
        // the negotiation early if the remote party ever sends an offer that
207
        // we've sent in the past. Once negotiation terminates, we can extract
208
        // the prior signature of our accepted offer from this map.
209
        //
210
        // TODO(roasbeef): need to ensure if they broadcast w/ any of our prior
211
        // sigs, we are aware of
212
        priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned
213

214
        // closeReq is the initial closing request. This will only be populated
215
        // if we're the initiator of this closing negotiation.
216
        //
217
        // TODO(roasbeef): abstract away
218
        closeReq *htlcswitch.ChanClose
219

220
        // localDeliveryScript is the script that we'll send our settled channel
221
        // funds to.
222
        localDeliveryScript []byte
223

224
        // localInternalKey is the local delivery address Taproot internal key,
225
        // if the local delivery script is a P2TR output.
226
        localInternalKey fn.Option[btcec.PublicKey]
227

228
        // remoteDeliveryScript is the script that we'll send the remote party's
229
        // settled channel funds to.
230
        remoteDeliveryScript []byte
231

232
        // closer is ChannelParty who initiated the coop close
233
        closer lntypes.ChannelParty
234

235
        // cachedClosingSigned is a cached copy of a received ClosingSigned that
236
        // we use to handle a specific race condition caused by the independent
237
        // message processing queues.
238
        cachedClosingSigned fn.Option[lnwire.ClosingSigned]
239

240
        // localCloseOutput is the local output on the closing transaction that
241
        // the local party should be paid to. This will only be populated if the
242
        // local balance isn't dust.
243
        localCloseOutput fn.Option[CloseOutput]
244

245
        // remoteCloseOutput is the remote output on the closing transaction
246
        // that the remote party should be paid to. This will only be populated
247
        // if the remote balance isn't dust.
248
        remoteCloseOutput fn.Option[CloseOutput]
249

250
        // auxOutputs are the optional additional outputs that might be added to
251
        // the closing transaction.
252
        auxOutputs fn.Option[AuxCloseOutputs]
253
}
254

255
// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
256
// delivery scripts of both parties and our ideal fee rate.
257
func calcCoopCloseFee(chanType channeldb.ChannelType,
258
        localOutput, remoteOutput *wire.TxOut,
259
        idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
17✔
260

17✔
261
        var weightEstimator input.TxWeightEstimator
17✔
262

17✔
263
        if chanType.IsTaproot() {
17✔
264
                weightEstimator.AddWitnessInput(
×
265
                        input.TaprootSignatureWitnessSize,
×
266
                )
×
267
        } else {
17✔
268
                weightEstimator.AddWitnessInput(input.MultiSigWitnessSize)
17✔
269
        }
17✔
270

271
        // One of these outputs might be dust, so we'll skip adding it to our
272
        // mock transaction, so the fees are more accurate.
273
        if localOutput != nil {
34✔
274
                weightEstimator.AddTxOutput(localOutput)
17✔
275
        }
17✔
276
        if remoteOutput != nil {
34✔
277
                weightEstimator.AddTxOutput(remoteOutput)
17✔
278
        }
17✔
279

280
        totalWeight := weightEstimator.Weight()
17✔
281

17✔
282
        return idealFeeRate.FeeForWeight(totalWeight)
17✔
283
}
284

285
// SimpleCoopFeeEstimator is the default co-op close fee estimator. It assumes
286
// a normal segwit v0 channel, and that no outputs on the closing transaction
287
// are dust.
288
type SimpleCoopFeeEstimator struct {
289
}
290

291
// EstimateFee estimates an _absolute_ fee for a co-op close transaction given
292
// the local+remote tx outs (for the co-op close transaction), channel type,
293
// and ideal fee rate.
294
func (d *SimpleCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
295
        localTxOut, remoteTxOut *wire.TxOut,
296
        idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
17✔
297

17✔
298
        return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
17✔
299
}
17✔
300

301
// NewChanCloser creates a new instance of the channel closure given the passed
302
// configuration, and delivery+fee preference. The final argument should only
303
// be populated iff, we're the initiator of this closing request.
304
func NewChanCloser(cfg ChanCloseCfg, deliveryScript DeliveryAddrWithKey,
305
        idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
306
        closeReq *htlcswitch.ChanClose,
307
        closer lntypes.ChannelParty) *ChanCloser {
16✔
308

16✔
309
        chanPoint := cfg.Channel.ChannelPoint()
16✔
310
        cid := lnwire.NewChanIDFromOutPoint(chanPoint)
16✔
311
        return &ChanCloser{
16✔
312
                closeReq:            closeReq,
16✔
313
                state:               closeIdle,
16✔
314
                chanPoint:           chanPoint,
16✔
315
                cid:                 cid,
16✔
316
                cfg:                 cfg,
16✔
317
                negotiationHeight:   negotiationHeight,
16✔
318
                idealFeeRate:        idealFeePerKw,
16✔
319
                localInternalKey:    deliveryScript.InternalKey,
16✔
320
                localDeliveryScript: deliveryScript.DeliveryAddress,
16✔
321
                priorFeeOffers: make(
16✔
322
                        map[btcutil.Amount]*lnwire.ClosingSigned,
16✔
323
                ),
16✔
324
                closer: closer,
16✔
325
        }
16✔
326
}
16✔
327

328
// initFeeBaseline computes our ideal fee rate, and also the largest fee we'll
329
// accept given information about the delivery script of the remote party.
330
func (c *ChanCloser) initFeeBaseline() {
12✔
331
        // Depending on if a balance ends up being dust or not, we'll pass a
12✔
332
        // nil TxOut into the EstimateFee call which can handle it.
12✔
333
        var localTxOut, remoteTxOut *wire.TxOut
12✔
334
        if isDust, _ := c.cfg.Channel.LocalBalanceDust(); !isDust {
24✔
335
                localTxOut = &wire.TxOut{
12✔
336
                        PkScript: c.localDeliveryScript,
12✔
337
                        Value:    0,
12✔
338
                }
12✔
339
        }
12✔
340
        if isDust, _ := c.cfg.Channel.RemoteBalanceDust(); !isDust {
24✔
341
                remoteTxOut = &wire.TxOut{
12✔
342
                        PkScript: c.remoteDeliveryScript,
12✔
343
                        Value:    0,
12✔
344
                }
12✔
345
        }
12✔
346

347
        // Given the target fee-per-kw, we'll compute what our ideal _total_
348
        // fee will be starting at for this fee negotiation.
349
        c.idealFeeSat = c.cfg.FeeEstimator.EstimateFee(
12✔
350
                0, localTxOut, remoteTxOut, c.idealFeeRate,
12✔
351
        )
12✔
352

12✔
353
        // When we're the initiator, we'll want to also factor in the highest
12✔
354
        // fee we want to pay. This'll either be 3x the ideal fee, or the
12✔
355
        // specified explicit max fee.
12✔
356
        c.maxFee = c.idealFeeSat * defaultMaxFeeMultiplier
12✔
357
        if c.cfg.MaxFee > 0 {
17✔
358
                c.maxFee = c.cfg.FeeEstimator.EstimateFee(
5✔
359
                        0, localTxOut, remoteTxOut, c.cfg.MaxFee,
5✔
360
                )
5✔
361
        }
5✔
362

363
        chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
12✔
364
                "is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
12✔
365
                int64(c.idealFeeSat), int64(c.maxFee))
12✔
366
}
367

368
// initChanShutdown begins the shutdown process by un-registering the channel,
369
// and creating a valid shutdown message to our target delivery address.
370
func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
12✔
371
        // With both items constructed we'll now send the shutdown message for
12✔
372
        // this particular channel, advertising a shutdown request to our
12✔
373
        // desired closing script.
12✔
374
        shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
12✔
375

12✔
376
        // At this point, we'll check to see if we have any custom records to
12✔
377
        // add to the shutdown message.
12✔
378
        err := fn.MapOptionZ(c.cfg.AuxCloser, func(a AuxChanCloser) error {
12✔
379
                shutdownCustomRecords, err := a.ShutdownBlob(AuxShutdownReq{
×
380
                        ChanPoint:   c.chanPoint,
×
381
                        ShortChanID: c.cfg.Channel.ShortChanID(),
×
382
                        Initiator:   c.cfg.Channel.IsInitiator(),
×
383
                        InternalKey: c.localInternalKey,
×
384
                        CommitBlob:  c.cfg.Channel.LocalCommitmentBlob(),
×
385
                        FundingBlob: c.cfg.Channel.FundingBlob(),
×
386
                })
×
387
                if err != nil {
×
388
                        return err
×
389
                }
×
390

391
                shutdownCustomRecords.WhenSome(func(cr lnwire.CustomRecords) {
×
392
                        shutdown.CustomRecords = cr
×
393
                })
×
394

395
                return nil
×
396
        })
397
        if err != nil {
12✔
398
                return nil, err
×
399
        }
×
400

401
        // If this is a taproot channel, then we'll need to also generate a
402
        // nonce that'll be used sign the co-op close transaction offer.
403
        if c.cfg.Channel.ChanType().IsTaproot() {
15✔
404
                firstClosingNonce, err := c.cfg.MusigSession.ClosingNonce()
3✔
405
                if err != nil {
3✔
406
                        return nil, err
×
407
                }
×
408

409
                shutdown.ShutdownNonce = lnwire.SomeShutdownNonce(
3✔
410
                        firstClosingNonce.PubNonce,
3✔
411
                )
3✔
412

3✔
413
                chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
3✔
414
                        spew.Sdump(firstClosingNonce.PubNonce))
3✔
415
        }
416

417
        // Before closing, we'll attempt to send a disable update for the
418
        // channel.  We do so before closing the channel as otherwise the
419
        // current edge policy won't be retrievable from the graph.
420
        if err := c.cfg.DisableChannel(c.chanPoint); err != nil {
22✔
421
                chancloserLog.Warnf("Unable to disable channel %v on close: %v",
10✔
422
                        c.chanPoint, err)
10✔
423
        }
10✔
424

425
        chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
12✔
426
                c.chanPoint)
12✔
427

12✔
428
        // At this point, we persist any relevant info regarding the Shutdown
12✔
429
        // message we are about to send in order to ensure that if a
12✔
430
        // re-establish occurs then we will re-send the same Shutdown message.
12✔
431
        shutdownInfo := channeldb.NewShutdownInfo(
12✔
432
                c.localDeliveryScript, c.closer.IsLocal(),
12✔
433
        )
12✔
434
        err = c.cfg.Channel.MarkShutdownSent(shutdownInfo)
12✔
435
        if err != nil {
12✔
436
                return nil, err
×
437
        }
×
438

439
        // We'll track our local close output, even if it's dust in BTC terms,
440
        // it might still carry value in custom channel terms.
441
        _, dustAmt := c.cfg.Channel.LocalBalanceDust()
12✔
442
        localBalance, _ := c.cfg.Channel.CommitBalances()
12✔
443
        c.localCloseOutput = fn.Some(CloseOutput{
12✔
444
                Amt:             localBalance,
12✔
445
                DustLimit:       dustAmt,
12✔
446
                PkScript:        c.localDeliveryScript,
12✔
447
                ShutdownRecords: shutdown.CustomRecords,
12✔
448
        })
12✔
449

12✔
450
        return shutdown, nil
12✔
451
}
452

453
// ShutdownChan is the first method that's to be called by the initiator of the
454
// cooperative channel closure. This message returns the shutdown message to
455
// send to the remote party. Upon completion, we enter the
456
// closeShutdownInitiated phase as we await a response.
457
func (c *ChanCloser) ShutdownChan() (*lnwire.Shutdown, error) {
8✔
458
        // If we attempt to shutdown the channel for the first time, and we're not
8✔
459
        // in the closeIdle state, then the caller made an error.
8✔
460
        if c.state != closeIdle {
8✔
461
                return nil, ErrChanAlreadyClosing
×
462
        }
×
463

464
        chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
8✔
465

8✔
466
        shutdownMsg, err := c.initChanShutdown()
8✔
467
        if err != nil {
8✔
468
                return nil, err
×
469
        }
×
470

471
        // With the opening steps complete, we'll transition into the
472
        // closeShutdownInitiated state. In this state, we'll wait until the
473
        // other party sends their version of the shutdown message.
474
        c.state = closeShutdownInitiated
8✔
475

8✔
476
        // Finally, we'll return the shutdown message to the caller so it can
8✔
477
        // send it to the remote peer.
8✔
478
        return shutdownMsg, nil
8✔
479
}
480

481
// ClosingTx returns the fully signed, final closing transaction.
482
//
483
// NOTE: This transaction is only available if the state machine is in the
484
// closeFinished state.
485
func (c *ChanCloser) ClosingTx() (*wire.MsgTx, error) {
22✔
486
        // If the state machine hasn't finished closing the channel, then we'll
22✔
487
        // return an error as we haven't yet computed the closing tx.
22✔
488
        if c.state != closeFinished {
33✔
489
                return nil, ErrChanCloseNotFinished
11✔
490
        }
11✔
491

492
        return c.closingTx, nil
12✔
493
}
494

495
// CloseRequest returns the original close request that prompted the creation
496
// of the state machine.
497
//
498
// NOTE: This will only return a non-nil pointer if we were the initiator of
499
// the cooperative closure workflow.
500
func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
5✔
501
        return c.closeReq
5✔
502
}
5✔
503

504
// Channel returns the channel stored in the config as a
505
// *lnwallet.LightningChannel.
506
//
507
// NOTE: This method will PANIC if the underlying channel implementation isn't
508
// the desired type.
509
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
5✔
510
        // TODO(roasbeef): remove this
5✔
511
        return c.cfg.Channel.(*lnwallet.LightningChannel)
5✔
512
}
5✔
513

514
// NegotiationHeight returns the negotiation height.
515
func (c *ChanCloser) NegotiationHeight() uint32 {
5✔
516
        return c.negotiationHeight
5✔
517
}
5✔
518

519
// LocalCloseOutput returns the local close output.
520
func (c *ChanCloser) LocalCloseOutput() fn.Option[CloseOutput] {
5✔
521
        return c.localCloseOutput
5✔
522
}
5✔
523

524
// RemoteCloseOutput returns the remote close output.
525
func (c *ChanCloser) RemoteCloseOutput() fn.Option[CloseOutput] {
5✔
526
        return c.remoteCloseOutput
5✔
527
}
5✔
528

529
// AuxOutputs returns optional extra outputs.
530
func (c *ChanCloser) AuxOutputs() fn.Option[AuxCloseOutputs] {
5✔
531
        return c.auxOutputs
5✔
532
}
5✔
533

534
// validateShutdownScript attempts to match and validate the script provided in
535
// our peer's shutdown message with the upfront shutdown script we have on
536
// record. For any script specified, we also make sure it matches our
537
// requirements. If no upfront shutdown script was set, we do not need to
538
// enforce option upfront shutdown, so the function returns early. If an
539
// upfront script is set, we check whether it matches the script provided by
540
// our peer. If they do not match, we use the disconnect function provided to
541
// disconnect from the peer.
542
func validateShutdownScript(upfrontScript, peerScript lnwire.DeliveryAddress,
543
        netParams *chaincfg.Params) error {
33✔
544

33✔
545
        // Either way, we'll make sure that the script passed meets our
33✔
546
        // standards. The upfrontScript should have already been checked at an
33✔
547
        // earlier stage, but we'll repeat the check here for defense in depth.
33✔
548
        if len(upfrontScript) != 0 {
38✔
549
                if !lnwallet.ValidateUpfrontShutdown(upfrontScript, netParams) {
5✔
550
                        return ErrInvalidShutdownScript
×
551
                }
×
552
        }
553
        if len(peerScript) != 0 {
63✔
554
                if !lnwallet.ValidateUpfrontShutdown(peerScript, netParams) {
31✔
555
                        return ErrInvalidShutdownScript
1✔
556
                }
1✔
557
        }
558

559
        // If no upfront shutdown script was set, return early because we do
560
        // not need to enforce closure to a specific script.
561
        if len(upfrontScript) == 0 {
60✔
562
                return nil
28✔
563
        }
28✔
564

565
        // If an upfront shutdown script was provided, disconnect from the peer, as
566
        // per BOLT 2, and return an error.
567
        if !bytes.Equal(upfrontScript, peerScript) {
7✔
568
                chancloserLog.Warnf("peer's script: %x does not match upfront "+
2✔
569
                        "shutdown script: %x", peerScript, upfrontScript)
2✔
570

2✔
571
                return ErrUpfrontShutdownScriptMismatch
2✔
572
        }
2✔
573

574
        return nil
3✔
575
}
576

577
// ReceiveShutdown takes a raw Shutdown message and uses it to try and advance
578
// the ChanCloser state machine, failing if it is coming in at an invalid time.
579
// If appropriate, it will also generate a Shutdown message of its own to send
580
// out to the peer. It is possible for this method to return None when no error
581
// occurred.
582
func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) (
583
        fn.Option[lnwire.Shutdown], error) {
8✔
584

8✔
585
        noShutdown := fn.None[lnwire.Shutdown]()
8✔
586

8✔
587
        // We'll track their remote close output, even if it's dust in BTC
8✔
588
        // terms, it might still carry value in custom channel terms.
8✔
589
        _, dustAmt := c.cfg.Channel.RemoteBalanceDust()
8✔
590
        _, remoteBalance := c.cfg.Channel.CommitBalances()
8✔
591
        c.remoteCloseOutput = fn.Some(CloseOutput{
8✔
592
                Amt:             remoteBalance,
8✔
593
                DustLimit:       dustAmt,
8✔
594
                PkScript:        msg.Address,
8✔
595
                ShutdownRecords: msg.CustomRecords,
8✔
596
        })
8✔
597

8✔
598
        switch c.state {
8✔
599
        // If we're in the close idle state, and we're receiving a channel
600
        // closure related message, then this indicates that we're on the
601
        // receiving side of an initiated channel closure.
602
        case closeIdle:
5✔
603
                // As we're the responder to this shutdown (the other party
5✔
604
                // wants to close), we'll check if this is a frozen channel or
5✔
605
                // not. If the channel is frozen and we were not also the
5✔
606
                // initiator of the channel opening, then we'll deny their close
5✔
607
                // attempt.
5✔
608
                chanInitiator := c.cfg.Channel.IsInitiator()
5✔
609
                if !chanInitiator {
7✔
610
                        absoluteThawHeight, err :=
2✔
611
                                c.cfg.Channel.AbsoluteThawHeight()
2✔
612
                        if err != nil {
2✔
613
                                return noShutdown, err
×
614
                        }
×
615
                        if c.negotiationHeight < absoluteThawHeight {
2✔
616
                                return noShutdown, fmt.Errorf("initiator "+
×
617
                                        "attempting to co-op close frozen "+
×
618
                                        "ChannelPoint(%v) (current_height=%v, "+
×
619
                                        "thaw_height=%v)", c.chanPoint,
×
620
                                        c.negotiationHeight, absoluteThawHeight)
×
621
                        }
×
622
                }
623

624
                // If the remote node opened the channel with option upfront
625
                // shutdown script, check that the script they provided matches.
626
                if err := validateShutdownScript(
5✔
627
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
5✔
628
                        msg.Address, c.cfg.ChainParams,
5✔
629
                ); err != nil {
5✔
630
                        return noShutdown, err
×
631
                }
×
632

633
                // Once we have checked that the other party has not violated
634
                // option upfront shutdown we set their preference for delivery
635
                // address. We'll use this when we craft the closure
636
                // transaction.
637
                c.remoteDeliveryScript = msg.Address
5✔
638

5✔
639
                // We'll generate a shutdown message of our own to send across
5✔
640
                // the wire.
5✔
641
                localShutdown, err := c.initChanShutdown()
5✔
642
                if err != nil {
5✔
643
                        return noShutdown, err
×
644
                }
×
645

646
                // If this is a taproot channel, then we'll want to stash the
647
                // remote nonces so we can properly create a new musig
648
                // session for signing.
649
                if c.cfg.Channel.ChanType().IsTaproot() {
7✔
650
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
2✔
651
                                errNoShutdownNonce,
2✔
652
                        )
2✔
653
                        if err != nil {
2✔
654
                                return noShutdown, err
×
655
                        }
×
656

657
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
2✔
658
                                PubNonce: shutdownNonce,
2✔
659
                        })
2✔
660
                }
661

662
                chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
5✔
663
                        c.chanPoint)
5✔
664

5✔
665
                // After the other party receives this message, we'll actually
5✔
666
                // start the final stage of the closure process: fee
5✔
667
                // negotiation. So we'll update our internal state to reflect
5✔
668
                // this, so we can handle the next message sent.
5✔
669
                c.state = closeAwaitingFlush
5✔
670

5✔
671
                return fn.Some(*localShutdown), err
5✔
672

673
        case closeShutdownInitiated:
4✔
674
                // If the remote node opened the channel with option upfront
4✔
675
                // shutdown script, check that the script they provided matches.
4✔
676
                if err := validateShutdownScript(
4✔
677
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
4✔
678
                        msg.Address, c.cfg.ChainParams,
4✔
679
                ); err != nil {
4✔
680
                        return noShutdown, err
×
681
                }
×
682

683
                // Now that we know this is a valid shutdown message and
684
                // address, we'll record their preferred delivery closing
685
                // script.
686
                c.remoteDeliveryScript = msg.Address
4✔
687

4✔
688
                // At this point, we can now start the fee negotiation state, by
4✔
689
                // constructing and sending our initial signature for what we
4✔
690
                // think the closing transaction should look like.
4✔
691
                c.state = closeAwaitingFlush
4✔
692

4✔
693
                // If this is a taproot channel, then we'll want to stash the
4✔
694
                // local+remote nonces so we can properly create a new musig
4✔
695
                // session for signing.
4✔
696
                if c.cfg.Channel.ChanType().IsTaproot() {
6✔
697
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
2✔
698
                                errNoShutdownNonce,
2✔
699
                        )
2✔
700
                        if err != nil {
2✔
701
                                return noShutdown, err
×
702
                        }
×
703

704
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
2✔
705
                                PubNonce: shutdownNonce,
2✔
706
                        })
2✔
707
                }
708

709
                chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
4✔
710
                        "received, entering fee negotiation", c.chanPoint)
4✔
711

4✔
712
                return noShutdown, nil
4✔
713

714
        default:
×
715
                // Otherwise we are not in a state where we can accept this
×
716
                // message.
×
717
                return noShutdown, ErrInvalidState
×
718
        }
719
}
720

721
// BeginNegotiation should be called when we have definitively reached a clean
722
// channel state and are ready to cooperatively arrive at a closing transaction.
723
// If it is our responsibility to kick off the negotiation, this method will
724
// generate a ClosingSigned message. If it is the remote's responsibility, then
725
// it will not. In either case it will transition the ChanCloser state machine
726
// to the negotiation phase wherein ClosingSigned messages are exchanged until
727
// a mutually agreeable result is achieved.
728
func (c *ChanCloser) BeginNegotiation() (fn.Option[lnwire.ClosingSigned],
729
        error) {
8✔
730

8✔
731
        noClosingSigned := fn.None[lnwire.ClosingSigned]()
8✔
732

8✔
733
        switch c.state {
8✔
734
        case closeAwaitingFlush:
8✔
735
                // Now that we know their desired delivery script, we can
8✔
736
                // compute what our max/ideal fee will be.
8✔
737
                c.initFeeBaseline()
8✔
738

8✔
739
                // Before continuing, mark the channel as cooperatively closed
8✔
740
                // with a nil txn. Even though we haven't negotiated the final
8✔
741
                // txn, this guarantees that our listchannels rpc will be
8✔
742
                // externally consistent, and reflect that the channel is being
8✔
743
                // shutdown by the time the closing request returns.
8✔
744
                err := c.cfg.Channel.MarkCoopBroadcasted(
8✔
745
                        nil, c.closer,
8✔
746
                )
8✔
747
                if err != nil {
8✔
748
                        return noClosingSigned, err
×
749
                }
×
750

751
                // At this point, we can now start the fee negotiation state, by
752
                // constructing and sending our initial signature for what we
753
                // think the closing transaction should look like.
754
                c.state = closeFeeNegotiation
8✔
755

8✔
756
                if !c.cfg.Channel.IsInitiator() {
10✔
757
                        // By default this means we do nothing, but we do want
2✔
758
                        // to check if we have a cached remote offer to process.
2✔
759
                        // If we do, we'll process it here.
2✔
760
                        res := noClosingSigned
2✔
761
                        err = nil
2✔
762
                        c.cachedClosingSigned.WhenSome(
2✔
763
                                func(cs lnwire.ClosingSigned) {
3✔
764
                                        res, err = c.ReceiveClosingSigned(cs)
1✔
765
                                },
1✔
766
                        )
767

768
                        return res, err
2✔
769
                }
770

771
                // We'll craft our initial close proposal in order to keep the
772
                // negotiation moving, but only if we're the initiator.
773
                closingSigned, err := c.proposeCloseSigned(c.idealFeeSat)
7✔
774
                if err != nil {
7✔
775
                        return noClosingSigned,
×
776
                                fmt.Errorf("unable to sign new co op "+
×
777
                                        "close offer: %w", err)
×
778
                }
×
779

780
                return fn.Some(*closingSigned), nil
7✔
781

782
        default:
×
783
                return noClosingSigned, ErrInvalidState
×
784
        }
785
}
786

787
// ReceiveClosingSigned is a method that should be called whenever we receive a
788
// ClosingSigned message from the wire. It may or may not return a
789
// ClosingSigned of our own to send back to the remote.
790
func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
791
        msg lnwire.ClosingSigned) (fn.Option[lnwire.ClosingSigned], error) {
15✔
792

15✔
793
        noClosing := fn.None[lnwire.ClosingSigned]()
15✔
794

15✔
795
        switch c.state {
15✔
796
        case closeAwaitingFlush:
1✔
797
                // If we hit this case it either means there's a protocol
1✔
798
                // violation or that our chanCloser received the remote offer
1✔
799
                // before the link finished processing the channel flush.
1✔
800
                c.cachedClosingSigned = fn.Some(msg)
1✔
801
                return fn.None[lnwire.ClosingSigned](), nil
1✔
802

803
        case closeFeeNegotiation:
14✔
804
                // If this is a taproot channel, then it MUST have a partial
14✔
805
                // signature set at this point.
14✔
806
                isTaproot := c.cfg.Channel.ChanType().IsTaproot()
14✔
807
                if isTaproot && msg.PartialSig.IsNone() {
14✔
808
                        return noClosing,
×
809
                                fmt.Errorf("partial sig not set " +
×
810
                                        "for taproot chan")
×
811
                }
×
812

813
                isInitiator := c.cfg.Channel.IsInitiator()
14✔
814

14✔
815
                // We'll compare the proposed total fee, to what we've proposed
14✔
816
                // during the negotiations. If it doesn't match any of our
14✔
817
                // prior offers, then we'll attempt to ratchet the fee closer
14✔
818
                // to our ideal fee.
14✔
819
                remoteProposedFee := msg.FeeSatoshis
14✔
820

14✔
821
                _, feeMatchesOffer := c.priorFeeOffers[remoteProposedFee]
14✔
822
                switch {
14✔
823
                // For taproot channels, since nonces are involved, we can't do
824
                // the existing co-op close negotiation process without going
825
                // to a fully round based model. Rather than do this, we'll
826
                // just accept the very first offer by the initiator.
827
                case isTaproot && !isInitiator:
2✔
828
                        chancloserLog.Infof("ChannelPoint(%v) accepting "+
2✔
829
                                "initiator fee of %v", c.chanPoint,
2✔
830
                                remoteProposedFee)
2✔
831

2✔
832
                        // To auto-accept the initiators proposal, we'll just
2✔
833
                        // send back a signature w/ the same offer. We don't
2✔
834
                        // send the message here, as we can drop down and
2✔
835
                        // finalize the closure and broadcast, then echo back
2✔
836
                        // to Alice the final signature.
2✔
837
                        _, err := c.proposeCloseSigned(remoteProposedFee)
2✔
838
                        if err != nil {
2✔
839
                                return noClosing, fmt.Errorf("unable to sign "+
×
840
                                        "new co op close offer: %w", err)
×
841
                        }
×
842

843
                // Otherwise, if we are the initiator, and we just sent a
844
                // signature for a taproot channel, then we'll ensure that the
845
                // fee rate matches up exactly.
846
                case isTaproot && isInitiator && !feeMatchesOffer:
1✔
847
                        return noClosing,
1✔
848
                                fmt.Errorf("fee rate for "+
1✔
849
                                        "taproot channels was not accepted: "+
1✔
850
                                        "sent %v, got %v",
1✔
851
                                        c.idealFeeSat, remoteProposedFee)
1✔
852

853
                // If we're the initiator of the taproot channel, and we had
854
                // our fee echo'd back, then it's all good, and we can proceed
855
                // with final broadcast.
856
                case isTaproot && isInitiator && feeMatchesOffer:
2✔
857
                        break
2✔
858

859
                // Otherwise, if this is a normal segwit v0 channel, and the
860
                // fee doesn't match our offer, then we'll try to "negotiate" a
861
                // new fee.
862
                case !feeMatchesOffer:
7✔
863
                        // We'll now attempt to ratchet towards a fee deemed
7✔
864
                        // acceptable by both parties, factoring in our ideal
7✔
865
                        // fee rate, and the last proposed fee by both sides.
7✔
866
                        proposal := calcCompromiseFee(
7✔
867
                                c.chanPoint, c.idealFeeSat, c.lastFeeProposal,
7✔
868
                                remoteProposedFee,
7✔
869
                        )
7✔
870
                        if c.cfg.Channel.IsInitiator() && proposal > c.maxFee {
8✔
871
                                return noClosing, fmt.Errorf(
1✔
872
                                        "%w: %v > %v",
1✔
873
                                        ErrProposalExceedsMaxFee,
1✔
874
                                        proposal, c.maxFee)
1✔
875
                        }
1✔
876

877
                        // With our new fee proposal calculated, we'll craft a
878
                        // new close signed signature to send to the other
879
                        // party so we can continue the fee negotiation
880
                        // process.
881
                        closeSigned, err := c.proposeCloseSigned(proposal)
6✔
882
                        if err != nil {
7✔
883
                                return noClosing, fmt.Errorf("unable to sign "+
1✔
884
                                        "new co op close offer: %w", err)
1✔
885
                        }
1✔
886

887
                        // If the compromise fee doesn't match what the peer
888
                        // proposed, then we'll return this latest close signed
889
                        // message so we can continue negotiation.
890
                        if proposal != remoteProposedFee {
9✔
891
                                chancloserLog.Debugf("ChannelPoint(%v): close "+
4✔
892
                                        "tx fee disagreement, continuing "+
4✔
893
                                        "negotiation", c.chanPoint)
4✔
894

4✔
895
                                return fn.Some(*closeSigned), nil
4✔
896
                        }
4✔
897
                }
898

899
                chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, "+
7✔
900
                        "ending negotiation", c.chanPoint, remoteProposedFee)
7✔
901

7✔
902
                // Otherwise, we've agreed on a fee for the closing
7✔
903
                // transaction! We'll craft the final closing transaction so we
7✔
904
                // can broadcast it to the network.
7✔
905
                var (
7✔
906
                        localSig, remoteSig input.Signature
7✔
907
                        closeOpts           []lnwallet.ChanCloseOpt
7✔
908
                        err                 error
7✔
909
                )
7✔
910
                matchingSig := c.priorFeeOffers[remoteProposedFee]
7✔
911
                if c.cfg.Channel.ChanType().IsTaproot() {
10✔
912
                        localWireSig, err := matchingSig.PartialSig.UnwrapOrErrV( //nolint:ll
3✔
913
                                fmt.Errorf("none local sig"),
3✔
914
                        )
3✔
915
                        if err != nil {
3✔
916
                                return noClosing, err
×
917
                        }
×
918
                        remoteWireSig, err := msg.PartialSig.UnwrapOrErrV(
3✔
919
                                fmt.Errorf("none remote sig"),
3✔
920
                        )
3✔
921
                        if err != nil {
3✔
922
                                return noClosing, err
×
923
                        }
×
924

925
                        muSession := c.cfg.MusigSession
3✔
926
                        localSig, remoteSig, closeOpts, err = muSession.CombineClosingOpts( //nolint:ll
3✔
927
                                localWireSig, remoteWireSig,
3✔
928
                        )
3✔
929
                        if err != nil {
3✔
930
                                return noClosing, err
×
931
                        }
×
932
                } else {
5✔
933
                        localSig, err = matchingSig.Signature.ToSignature()
5✔
934
                        if err != nil {
5✔
935
                                return noClosing, err
×
936
                        }
×
937
                        remoteSig, err = msg.Signature.ToSignature()
5✔
938
                        if err != nil {
5✔
939
                                return noClosing, err
×
940
                        }
×
941
                }
942

943
                // Before we complete the cooperative close, we'll see if we
944
                // have any extra aux options.
945
                c.auxOutputs, err = c.auxCloseOutputs(remoteProposedFee)
7✔
946
                if err != nil {
7✔
947
                        return noClosing, err
×
948
                }
×
949
                c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
7✔
950
                        closeOpts = append(
×
951
                                closeOpts, lnwallet.WithExtraCloseOutputs(
×
952
                                        outs.ExtraCloseOutputs,
×
953
                                ),
×
954
                        )
×
955
                        closeOpts = append(
×
956
                                closeOpts, lnwallet.WithCustomCoopSort(
×
957
                                        outs.CustomSort,
×
958
                                ),
×
959
                        )
×
960
                })
×
961

962
                closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
7✔
963
                        localSig, remoteSig, c.localDeliveryScript,
7✔
964
                        c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
7✔
965
                )
7✔
966
                if err != nil {
7✔
967
                        return noClosing, err
×
968
                }
×
969
                c.closingTx = closeTx
7✔
970

7✔
971
                // If there's an aux chan closer, then we'll finalize with it
7✔
972
                // before we write to disk.
7✔
973
                err = fn.MapOptionZ(
7✔
974
                        c.cfg.AuxCloser, func(aux AuxChanCloser) error {
7✔
975
                                channel := c.cfg.Channel
×
976
                                //nolint:ll
×
977
                                req := AuxShutdownReq{
×
978
                                        ChanPoint:   c.chanPoint,
×
979
                                        ShortChanID: c.cfg.Channel.ShortChanID(),
×
980
                                        InternalKey: c.localInternalKey,
×
981
                                        Initiator:   channel.IsInitiator(),
×
982
                                        CommitBlob:  channel.LocalCommitmentBlob(),
×
983
                                        FundingBlob: channel.FundingBlob(),
×
984
                                }
×
985
                                desc := AuxCloseDesc{
×
986
                                        AuxShutdownReq:    req,
×
987
                                        LocalCloseOutput:  c.localCloseOutput,
×
988
                                        RemoteCloseOutput: c.remoteCloseOutput,
×
989
                                }
×
990

×
991
                                return aux.FinalizeClose(desc, closeTx)
×
992
                        },
×
993
                )
994
                if err != nil {
7✔
995
                        return noClosing, err
×
996
                }
×
997

998
                // Before publishing the closing tx, we persist it to the
999
                // database, such that it can be republished if something goes
1000
                // wrong.
1001
                err = c.cfg.Channel.MarkCoopBroadcasted(
7✔
1002
                        closeTx, c.closer,
7✔
1003
                )
7✔
1004
                if err != nil {
7✔
UNCOV
1005
                        return noClosing, err
×
UNCOV
1006
                }
×
1007

1008
                // With the closing transaction crafted, we'll now broadcast it
1009
                // to the network.
1010
                chancloserLog.Infof("Broadcasting cooperative close tx: %v",
7✔
1011
                        lnutils.SpewLogClosure(closeTx))
7✔
1012

7✔
1013
                // Create a close channel label.
7✔
1014
                chanID := c.cfg.Channel.ShortChanID()
7✔
1015
                closeLabel := labels.MakeLabel(
7✔
1016
                        labels.LabelTypeChannelClose, &chanID,
7✔
1017
                )
7✔
1018

7✔
1019
                if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
7✔
1020
                        return noClosing, err
×
1021
                }
×
1022

1023
                // Finally, we'll transition to the closeFinished state, and
1024
                // also return the final close signed message we sent.
1025
                // Additionally, we return true for the second argument to
1026
                // indicate we're finished with the channel closing
1027
                // negotiation.
1028
                c.state = closeFinished
7✔
1029
                matchingOffer := c.priorFeeOffers[remoteProposedFee]
7✔
1030

7✔
1031
                return fn.Some(*matchingOffer), nil
7✔
1032

1033
        // If we received a message while in the closeFinished state, then this
1034
        // should only be the remote party echoing the last ClosingSigned
1035
        // message that we agreed on.
1036
        case closeFinished:
2✔
1037

2✔
1038
                // There's no more to do as both sides should have already
2✔
1039
                // broadcast the closing transaction at this state.
2✔
1040
                return noClosing, nil
2✔
1041

1042
        default:
×
1043
                return noClosing, ErrInvalidState
×
1044
        }
1045
}
1046

1047
// auxCloseOutputs returns any additional outputs that should be used when
1048
// closing the channel.
1049
func (c *ChanCloser) auxCloseOutputs(
1050
        closeFee btcutil.Amount) (fn.Option[AuxCloseOutputs], error) {
19✔
1051

19✔
1052
        var closeOuts fn.Option[AuxCloseOutputs]
19✔
1053
        err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error {
19✔
1054
                req := AuxShutdownReq{
×
1055
                        ChanPoint:   c.chanPoint,
×
1056
                        ShortChanID: c.cfg.Channel.ShortChanID(),
×
1057
                        InternalKey: c.localInternalKey,
×
1058
                        Initiator:   c.cfg.Channel.IsInitiator(),
×
1059
                        CommitBlob:  c.cfg.Channel.LocalCommitmentBlob(),
×
1060
                        FundingBlob: c.cfg.Channel.FundingBlob(),
×
1061
                }
×
1062
                outs, err := aux.AuxCloseOutputs(AuxCloseDesc{
×
1063
                        AuxShutdownReq:    req,
×
1064
                        CloseFee:          closeFee,
×
1065
                        CommitFee:         c.cfg.Channel.CommitFee(),
×
1066
                        LocalCloseOutput:  c.localCloseOutput,
×
1067
                        RemoteCloseOutput: c.remoteCloseOutput,
×
1068
                })
×
1069
                if err != nil {
×
1070
                        return err
×
1071
                }
×
1072

1073
                closeOuts = outs
×
1074

×
1075
                return nil
×
1076
        })
1077
        if err != nil {
19✔
1078
                return closeOuts, err
×
1079
        }
×
1080

1081
        return closeOuts, nil
19✔
1082
}
1083

1084
// proposeCloseSigned attempts to propose a new signature for the closing
1085
// transaction for a channel based on the prior fee negotiations and our
1086
// current compromise fee.
1087
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
1088
        *lnwire.ClosingSigned, error) {
13✔
1089

13✔
1090
        var (
13✔
1091
                closeOpts []lnwallet.ChanCloseOpt
13✔
1092
                err       error
13✔
1093
        )
13✔
1094

13✔
1095
        // If this is a taproot channel, then we'll include the musig session
13✔
1096
        // generated for the next co-op close negotiation round.
13✔
1097
        if c.cfg.Channel.ChanType().IsTaproot() {
16✔
1098
                closeOpts, err = c.cfg.MusigSession.ProposalClosingOpts()
3✔
1099
                if err != nil {
3✔
1100
                        return nil, err
×
1101
                }
×
1102
        }
1103

1104
        // We'll also now see if the aux chan closer has any additional options
1105
        // for the closing purpose.
1106
        c.auxOutputs, err = c.auxCloseOutputs(fee)
13✔
1107
        if err != nil {
13✔
1108
                return nil, err
×
1109
        }
×
1110
        c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
13✔
1111
                closeOpts = append(
×
1112
                        closeOpts, lnwallet.WithExtraCloseOutputs(
×
1113
                                outs.ExtraCloseOutputs,
×
1114
                        ),
×
1115
                )
×
1116
                closeOpts = append(
×
1117
                        closeOpts, lnwallet.WithCustomCoopSort(
×
1118
                                outs.CustomSort,
×
1119
                        ),
×
1120
                )
×
1121
        })
×
1122

1123
        // With all our options added, we'll attempt to co-op close now.
1124
        rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
13✔
1125
                fee, c.localDeliveryScript, c.remoteDeliveryScript,
13✔
1126
                closeOpts...,
13✔
1127
        )
13✔
1128
        if err != nil {
13✔
1129
                return nil, err
×
1130
        }
×
1131

1132
        // We'll note our last signature and proposed fee so when the remote
1133
        // party responds we'll be able to decide if we've agreed on fees or
1134
        // not.
1135
        var (
13✔
1136
                parsedSig  lnwire.Sig
13✔
1137
                partialSig *lnwire.PartialSigWithNonce
13✔
1138
        )
13✔
1139
        if c.cfg.Channel.ChanType().IsTaproot() {
16✔
1140
                musig, ok := rawSig.(*lnwallet.MusigPartialSig)
3✔
1141
                if !ok {
3✔
1142
                        return nil, fmt.Errorf("expected MusigPartialSig, "+
×
1143
                                "got %T", rawSig)
×
1144
                }
×
1145

1146
                partialSig = musig.ToWireSig()
3✔
1147
        } else {
11✔
1148
                parsedSig, err = lnwire.NewSigFromSignature(rawSig)
11✔
1149
                if err != nil {
12✔
1150
                        return nil, err
1✔
1151
                }
1✔
1152
        }
1153

1154
        c.lastFeeProposal = fee
12✔
1155

12✔
1156
        chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+
12✔
1157
                "close chan", c.chanPoint, int64(fee))
12✔
1158

12✔
1159
        // We'll assemble a ClosingSigned message using this information and
12✔
1160
        // return it to the caller so we can kick off the final stage of the
12✔
1161
        // channel closure process.
12✔
1162
        closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig)
12✔
1163

12✔
1164
        // For musig2 channels, the main sig is blank, and instead we'll send
12✔
1165
        // over a partial signature which'll be combined once our offer is
12✔
1166
        // accepted.
12✔
1167
        if partialSig != nil {
15✔
1168
                closeSignedMsg.PartialSig = lnwire.SomePartialSig(
3✔
1169
                        partialSig.PartialSig,
3✔
1170
                )
3✔
1171
        }
3✔
1172

1173
        // We'll also save this close signed, in the case that the remote party
1174
        // accepts our offer. This way, we don't have to re-sign.
1175
        c.priorFeeOffers[fee] = closeSignedMsg
12✔
1176

12✔
1177
        return closeSignedMsg, nil
12✔
1178
}
1179

1180
// feeInAcceptableRange returns true if the passed remote fee is deemed to be
1181
// in an "acceptable" range to our local fee. This is an attempt at a
1182
// compromise and to ensure that the fee negotiation has a stopping point. We
1183
// consider their fee acceptable if it's within 30% of our fee.
1184
func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool {
6✔
1185
        // If our offer is lower than theirs, then we'll accept their offer if
6✔
1186
        // it's no more than 30% *greater* than our current offer.
6✔
1187
        if localFee < remoteFee {
12✔
1188
                acceptableRange := localFee + ((localFee * 3) / 10)
6✔
1189
                return remoteFee <= acceptableRange
6✔
1190
        }
6✔
1191

1192
        // If our offer is greater than theirs, then we'll accept their offer if
1193
        // it's no more than 30% *less* than our current offer.
1194
        acceptableRange := localFee - ((localFee * 3) / 10)
×
1195
        return remoteFee >= acceptableRange
×
1196
}
1197

1198
// ratchetFee is our step function used to inch our fee closer to something
1199
// that both sides can agree on. If up is true, then we'll attempt to increase
1200
// our offered fee. Otherwise, if up is false, then we'll attempt to decrease
1201
// our offered fee.
1202
func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount {
6✔
1203
        // If we need to ratchet up, then we'll increase our fee by 10%.
6✔
1204
        if up {
12✔
1205
                return fee + ((fee * 1) / 10)
6✔
1206
        }
6✔
1207

1208
        // Otherwise, we'll *decrease* our fee by 10%.
1209
        return fee - ((fee * 1) / 10)
×
1210
}
1211

1212
// calcCompromiseFee performs the current fee negotiation algorithm, taking
1213
// into consideration our ideal fee based on current fee environment, the fee
1214
// we last proposed (if any), and the fee proposed by the peer.
1215
func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
1216
        remoteFee btcutil.Amount) btcutil.Amount {
7✔
1217

7✔
1218
        // TODO(roasbeef): take in number of rounds as well?
7✔
1219

7✔
1220
        chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, "+
7✔
1221
                "ideal=%v, last_sent=%v, remote_offer=%v", chanPoint,
7✔
1222
                int64(ourIdealFee), int64(lastSentFee), int64(remoteFee))
7✔
1223

7✔
1224
        // Otherwise, we'll need to attempt to make a fee compromise if this is
7✔
1225
        // the second round, and neither side has agreed on fees.
7✔
1226
        switch {
7✔
1227
        // If their proposed fee is identical to our ideal fee, then we'll go
1228
        // with that as we can short circuit the fee negotiation. Similarly, if
1229
        // we haven't sent an offer yet, we'll default to our ideal fee.
1230
        case ourIdealFee == remoteFee || lastSentFee == 0:
1✔
1231
                return ourIdealFee
1✔
1232

1233
        // If the last fee we sent, is equal to the fee the remote party is
1234
        // offering, then we can simply return this fee as the negotiation is
1235
        // over.
1236
        case remoteFee == lastSentFee:
×
1237
                return lastSentFee
×
1238

1239
        // If the fee the remote party is offering is less than the last one we
1240
        // sent, then we'll need to ratchet down in order to move our offer
1241
        // closer to theirs.
1242
        case remoteFee < lastSentFee:
×
1243
                // If the fee is lower, but still acceptable, then we'll just
×
1244
                // return this fee and end the negotiation.
×
1245
                if feeInAcceptableRange(lastSentFee, remoteFee) {
×
1246
                        chancloserLog.Infof("ChannelPoint(%v): proposed "+
×
1247
                                "remote fee is close enough, capitulating",
×
1248
                                chanPoint)
×
1249

×
1250
                        return remoteFee
×
1251
                }
×
1252

1253
                // Otherwise, we'll ratchet the fee *down* using our current
1254
                // algorithm.
1255
                return ratchetFee(lastSentFee, false)
×
1256

1257
        // If the fee the remote party is offering is greater than the last one
1258
        // we sent, then we'll ratchet up in order to ensure we terminate
1259
        // eventually.
1260
        case remoteFee > lastSentFee:
6✔
1261
                // If the fee is greater, but still acceptable, then we'll just
6✔
1262
                // return this fee in order to put an end to the negotiation.
6✔
1263
                if feeInAcceptableRange(lastSentFee, remoteFee) {
6✔
1264
                        chancloserLog.Infof("ChannelPoint(%v): proposed "+
×
1265
                                "remote fee is close enough, capitulating",
×
1266
                                chanPoint)
×
1267

×
1268
                        return remoteFee
×
1269
                }
×
1270

1271
                // Otherwise, we'll ratchet the fee up using our current
1272
                // algorithm.
1273
                return ratchetFee(lastSentFee, true)
6✔
1274

1275
        default:
×
1276
                // TODO(roasbeef): fail if their fee isn't in expected range
×
1277
                return remoteFee
×
1278
        }
1279
}
1280

1281
// ParseUpfrontShutdownAddress attempts to parse an upfront shutdown address.
1282
// If the address is empty, it returns nil. If it successfully decoded the
1283
// address, it returns a script that pays out to the address.
1284
func ParseUpfrontShutdownAddress(address string,
1285
        params *chaincfg.Params) (lnwire.DeliveryAddress, error) {
16✔
1286

16✔
1287
        if len(address) == 0 {
24✔
1288
                return nil, nil
8✔
1289
        }
8✔
1290

1291
        addr, err := btcutil.DecodeAddress(
9✔
1292
                address, params,
9✔
1293
        )
9✔
1294
        if err != nil {
11✔
1295
                return nil, fmt.Errorf("invalid address: %w", err)
2✔
1296
        }
2✔
1297

1298
        if !addr.IsForNet(params) {
8✔
1299
                return nil, fmt.Errorf("invalid address: %v is not a %s "+
1✔
1300
                        "address", addr, params.Name)
1✔
1301
        }
1✔
1302

1303
        return txscript.PayToAddrScript(addr)
6✔
1304
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc