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

lightningnetwork / lnd / 12231552240

09 Dec 2024 08:17AM UTC coverage: 58.955% (+0.02%) from 58.933%
12231552240

Pull #9242

github

aakselrod
go.mod: update btcwallet to latest to eliminate waddrmgr deadlock
Pull Request #9242: Reapply #8644

24 of 40 new or added lines in 3 files covered. (60.0%)

89 existing lines in 18 files now uncovered.

133525 of 226485 relevant lines covered (58.96%)

19398.62 hits per line

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

74.73
/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"
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 {
20✔
260

20✔
261
        var weightEstimator input.TxWeightEstimator
20✔
262

20✔
263
        if chanType.IsTaproot() {
20✔
264
                weightEstimator.AddWitnessInput(
×
265
                        input.TaprootSignatureWitnessSize,
×
266
                )
×
267
        } else {
20✔
268
                weightEstimator.AddWitnessInput(input.MultiSigWitnessSize)
20✔
269
        }
20✔
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 {
40✔
274
                weightEstimator.AddTxOutput(localOutput)
20✔
275
        }
20✔
276
        if remoteOutput != nil {
40✔
277
                weightEstimator.AddTxOutput(remoteOutput)
20✔
278
        }
20✔
279

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

20✔
282
        return idealFeeRate.FeeForWeight(totalWeight)
20✔
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 {
20✔
297

20✔
298
        return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
20✔
299
}
20✔
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 {
19✔
308

19✔
309
        chanPoint := cfg.Channel.ChannelPoint()
19✔
310
        cid := lnwire.NewChanIDFromOutPoint(chanPoint)
19✔
311
        return &ChanCloser{
19✔
312
                closeReq:            closeReq,
19✔
313
                state:               closeIdle,
19✔
314
                chanPoint:           chanPoint,
19✔
315
                cid:                 cid,
19✔
316
                cfg:                 cfg,
19✔
317
                negotiationHeight:   negotiationHeight,
19✔
318
                idealFeeRate:        idealFeePerKw,
19✔
319
                localInternalKey:    deliveryScript.InternalKey,
19✔
320
                localDeliveryScript: deliveryScript.DeliveryAddress,
19✔
321
                priorFeeOffers: make(
19✔
322
                        map[btcutil.Amount]*lnwire.ClosingSigned,
19✔
323
                ),
19✔
324
                closer: closer,
19✔
325
        }
19✔
326
}
19✔
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() {
15✔
331
        // Depending on if a balance ends up being dust or not, we'll pass a
15✔
332
        // nil TxOut into the EstimateFee call which can handle it.
15✔
333
        var localTxOut, remoteTxOut *wire.TxOut
15✔
334
        if isDust, _ := c.cfg.Channel.LocalBalanceDust(); !isDust {
30✔
335
                localTxOut = &wire.TxOut{
15✔
336
                        PkScript: c.localDeliveryScript,
15✔
337
                        Value:    0,
15✔
338
                }
15✔
339
        }
15✔
340
        if isDust, _ := c.cfg.Channel.RemoteBalanceDust(); !isDust {
30✔
341
                remoteTxOut = &wire.TxOut{
15✔
342
                        PkScript: c.remoteDeliveryScript,
15✔
343
                        Value:    0,
15✔
344
                }
15✔
345
        }
15✔
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(
15✔
350
                0, localTxOut, remoteTxOut, c.idealFeeRate,
15✔
351
        )
15✔
352

15✔
353
        // When we're the initiator, we'll want to also factor in the highest
15✔
354
        // fee we want to pay. This'll either be 3x the ideal fee, or the
15✔
355
        // specified explicit max fee.
15✔
356
        c.maxFee = c.idealFeeSat * defaultMaxFeeMultiplier
15✔
357
        if c.cfg.MaxFee > 0 {
20✔
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) "+
15✔
364
                "is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
15✔
365
                int64(c.idealFeeSat), int64(c.maxFee))
15✔
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) {
15✔
371
        // With both items constructed we'll now send the shutdown message for
15✔
372
        // this particular channel, advertising a shutdown request to our
15✔
373
        // desired closing script.
15✔
374
        shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
15✔
375

15✔
376
        // At this point, we'll check to see if we have any custom records to
15✔
377
        // add to the shutdown message.
15✔
378
        err := fn.MapOptionZ(c.cfg.AuxCloser, func(a AuxChanCloser) error {
15✔
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 {
15✔
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() {
21✔
404
                firstClosingNonce, err := c.cfg.MusigSession.ClosingNonce()
6✔
405
                if err != nil {
6✔
406
                        return nil, err
×
407
                }
×
408

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

6✔
413
                chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
6✔
414
                        spew.Sdump(firstClosingNonce.PubNonce))
6✔
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 {
27✔
421
                chancloserLog.Warnf("Unable to disable channel %v on close: %v",
12✔
422
                        c.chanPoint, err)
12✔
423
        }
12✔
424

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

15✔
428
        // At this point, we persist any relevant info regarding the Shutdown
15✔
429
        // message we are about to send in order to ensure that if a
15✔
430
        // re-establish occurs then we will re-send the same Shutdown message.
15✔
431
        shutdownInfo := channeldb.NewShutdownInfo(
15✔
432
                c.localDeliveryScript, c.closer.IsLocal(),
15✔
433
        )
15✔
434
        err = c.cfg.Channel.MarkShutdownSent(shutdownInfo)
15✔
435
        if err != nil {
15✔
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()
15✔
442
        localBalance, _ := c.cfg.Channel.CommitBalances()
15✔
443
        c.localCloseOutput = fn.Some(CloseOutput{
15✔
444
                Amt:             localBalance,
15✔
445
                DustLimit:       dustAmt,
15✔
446
                PkScript:        c.localDeliveryScript,
15✔
447
                ShutdownRecords: shutdown.CustomRecords,
15✔
448
        })
15✔
449

15✔
450
        return shutdown, nil
15✔
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) {
11✔
458
        // If we attempt to shutdown the channel for the first time, and we're not
11✔
459
        // in the closeIdle state, then the caller made an error.
11✔
460
        if c.state != closeIdle {
11✔
461
                return nil, ErrChanAlreadyClosing
×
462
        }
×
463

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

11✔
466
        shutdownMsg, err := c.initChanShutdown()
11✔
467
        if err != nil {
11✔
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
11✔
475

11✔
476
        // Finally, we'll return the shutdown message to the caller so it can
11✔
477
        // send it to the remote peer.
11✔
478
        return shutdownMsg, nil
11✔
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) {
25✔
486
        // If the state machine hasn't finished closing the channel, then we'll
25✔
487
        // return an error as we haven't yet computed the closing tx.
25✔
488
        if c.state != closeFinished {
39✔
489
                return nil, ErrChanCloseNotFinished
14✔
490
        }
14✔
491

492
        return c.closingTx, nil
15✔
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 {
8✔
501
        return c.closeReq
8✔
502
}
8✔
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 {
8✔
510
        // TODO(roasbeef): remove this
8✔
511
        return c.cfg.Channel.(*lnwallet.LightningChannel)
8✔
512
}
8✔
513

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

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

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

529
// AuxOutputs returns optional extra outputs.
530
func (c *ChanCloser) AuxOutputs() fn.Option[AuxCloseOutputs] {
8✔
531
        return c.auxOutputs
8✔
532
}
8✔
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(disconnect func() error, upfrontScript,
543
        peerScript lnwire.DeliveryAddress, netParams *chaincfg.Params) error {
34✔
544

34✔
545
        // Either way, we'll make sure that the script passed meets our
34✔
546
        // standards. The upfrontScript should have already been checked at an
34✔
547
        // earlier stage, but we'll repeat the check here for defense in depth.
34✔
548
        if len(upfrontScript) != 0 {
40✔
549
                if !lnwallet.ValidateUpfrontShutdown(upfrontScript, netParams) {
6✔
550
                        return ErrInvalidShutdownScript
×
551
                }
×
552
        }
553
        if len(peerScript) != 0 {
65✔
554
                if !lnwallet.ValidateUpfrontShutdown(peerScript, netParams) {
32✔
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 {
64✔
562
                return nil
31✔
563
        }
31✔
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 "+
1✔
569
                        "shutdown script: %x", peerScript, upfrontScript)
1✔
570

1✔
571
                // Disconnect from the peer because they have violated option upfront
1✔
572
                // shutdown.
1✔
573
                if err := disconnect(); err != nil {
1✔
574
                        return err
×
575
                }
×
576

577
                return ErrUpfrontShutdownScriptMismatch
1✔
578
        }
579

580
        return nil
5✔
581
}
582

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

11✔
591
        noShutdown := fn.None[lnwire.Shutdown]()
11✔
592

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

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

630
                // If the remote node opened the channel with option upfront
631
                // shutdown script, check that the script they provided matches.
632
                if err := validateShutdownScript(
8✔
633
                        c.cfg.Disconnect,
8✔
634
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
8✔
635
                        msg.Address, c.cfg.ChainParams,
8✔
636
                ); err != nil {
8✔
637
                        return noShutdown, err
×
638
                }
×
639

640
                // Once we have checked that the other party has not violated
641
                // option upfront shutdown we set their preference for delivery
642
                // address. We'll use this when we craft the closure
643
                // transaction.
644
                c.remoteDeliveryScript = msg.Address
8✔
645

8✔
646
                // We'll generate a shutdown message of our own to send across
8✔
647
                // the wire.
8✔
648
                localShutdown, err := c.initChanShutdown()
8✔
649
                if err != nil {
8✔
650
                        return noShutdown, err
×
651
                }
×
652

653
                // If this is a taproot channel, then we'll want to stash the
654
                // remote nonces so we can properly create a new musig
655
                // session for signing.
656
                if c.cfg.Channel.ChanType().IsTaproot() {
13✔
657
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
5✔
658
                                errNoShutdownNonce,
5✔
659
                        )
5✔
660
                        if err != nil {
5✔
661
                                return noShutdown, err
×
662
                        }
×
663

664
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
5✔
665
                                PubNonce: shutdownNonce,
5✔
666
                        })
5✔
667
                }
668

669
                chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
8✔
670
                        c.chanPoint)
8✔
671

8✔
672
                // After the other party receives this message, we'll actually
8✔
673
                // start the final stage of the closure process: fee
8✔
674
                // negotiation. So we'll update our internal state to reflect
8✔
675
                // this, so we can handle the next message sent.
8✔
676
                c.state = closeAwaitingFlush
8✔
677

8✔
678
                return fn.Some(*localShutdown), err
8✔
679

680
        case closeShutdownInitiated:
7✔
681
                // If the remote node opened the channel with option upfront
7✔
682
                // shutdown script, check that the script they provided matches.
7✔
683
                if err := validateShutdownScript(
7✔
684
                        c.cfg.Disconnect,
7✔
685
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
7✔
686
                        msg.Address, c.cfg.ChainParams,
7✔
687
                ); err != nil {
7✔
688
                        return noShutdown, err
×
689
                }
×
690

691
                // Now that we know this is a valid shutdown message and
692
                // address, we'll record their preferred delivery closing
693
                // script.
694
                c.remoteDeliveryScript = msg.Address
7✔
695

7✔
696
                // At this point, we can now start the fee negotiation state, by
7✔
697
                // constructing and sending our initial signature for what we
7✔
698
                // think the closing transaction should look like.
7✔
699
                c.state = closeAwaitingFlush
7✔
700

7✔
701
                // If this is a taproot channel, then we'll want to stash the
7✔
702
                // local+remote nonces so we can properly create a new musig
7✔
703
                // session for signing.
7✔
704
                if c.cfg.Channel.ChanType().IsTaproot() {
12✔
705
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
5✔
706
                                errNoShutdownNonce,
5✔
707
                        )
5✔
708
                        if err != nil {
5✔
709
                                return noShutdown, err
×
710
                        }
×
711

712
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
5✔
713
                                PubNonce: shutdownNonce,
5✔
714
                        })
5✔
715
                }
716

717
                chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
7✔
718
                        "received, entering fee negotiation", c.chanPoint)
7✔
719

7✔
720
                return noShutdown, nil
7✔
721

722
        default:
×
723
                // Otherwise we are not in a state where we can accept this
×
724
                // message.
×
725
                return noShutdown, ErrInvalidState
×
726
        }
727
}
728

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

11✔
739
        noClosingSigned := fn.None[lnwire.ClosingSigned]()
11✔
740

11✔
741
        switch c.state {
11✔
742
        case closeAwaitingFlush:
11✔
743
                // Now that we know their desired delivery script, we can
11✔
744
                // compute what our max/ideal fee will be.
11✔
745
                c.initFeeBaseline()
11✔
746

11✔
747
                // Before continuing, mark the channel as cooperatively closed
11✔
748
                // with a nil txn. Even though we haven't negotiated the final
11✔
749
                // txn, this guarantees that our listchannels rpc will be
11✔
750
                // externally consistent, and reflect that the channel is being
11✔
751
                // shutdown by the time the closing request returns.
11✔
752
                err := c.cfg.Channel.MarkCoopBroadcasted(
11✔
753
                        nil, c.closer,
11✔
754
                )
11✔
755
                if err != nil {
11✔
UNCOV
756
                        return noClosingSigned, err
×
UNCOV
757
                }
×
758

759
                // At this point, we can now start the fee negotiation state, by
760
                // constructing and sending our initial signature for what we
761
                // think the closing transaction should look like.
762
                c.state = closeFeeNegotiation
11✔
763

11✔
764
                if !c.cfg.Channel.IsInitiator() {
16✔
765
                        // By default this means we do nothing, but we do want
5✔
766
                        // to check if we have a cached remote offer to process.
5✔
767
                        // If we do, we'll process it here.
5✔
768
                        res := noClosingSigned
5✔
769
                        err = nil
5✔
770
                        c.cachedClosingSigned.WhenSome(
5✔
771
                                func(cs lnwire.ClosingSigned) {
8✔
772
                                        res, err = c.ReceiveClosingSigned(cs)
3✔
773
                                },
3✔
774
                        )
775

776
                        return res, err
5✔
777
                }
778

779
                // We'll craft our initial close proposal in order to keep the
780
                // negotiation moving, but only if we're the initiator.
781
                closingSigned, err := c.proposeCloseSigned(c.idealFeeSat)
10✔
782
                if err != nil {
10✔
783
                        return noClosingSigned,
×
784
                                fmt.Errorf("unable to sign new co op "+
×
785
                                        "close offer: %w", err)
×
786
                }
×
787

788
                return fn.Some(*closingSigned), nil
10✔
789

790
        default:
×
791
                return noClosingSigned, ErrInvalidState
×
792
        }
793
}
794

795
// ReceiveClosingSigned is a method that should be called whenever we receive a
796
// ClosingSigned message from the wire. It may or may not return a
797
// ClosingSigned of our own to send back to the remote.
798
func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
799
        msg lnwire.ClosingSigned) (fn.Option[lnwire.ClosingSigned], error) {
18✔
800

18✔
801
        noClosing := fn.None[lnwire.ClosingSigned]()
18✔
802

18✔
803
        switch c.state {
18✔
804
        case closeAwaitingFlush:
3✔
805
                // If we hit this case it either means there's a protocol
3✔
806
                // violation or that our chanCloser received the remote offer
3✔
807
                // before the link finished processing the channel flush.
3✔
808
                c.cachedClosingSigned = fn.Some(msg)
3✔
809
                return fn.None[lnwire.ClosingSigned](), nil
3✔
810

811
        case closeFeeNegotiation:
17✔
812
                // If this is a taproot channel, then it MUST have a partial
17✔
813
                // signature set at this point.
17✔
814
                isTaproot := c.cfg.Channel.ChanType().IsTaproot()
17✔
815
                if isTaproot && msg.PartialSig.IsNone() {
17✔
816
                        return noClosing,
×
817
                                fmt.Errorf("partial sig not set " +
×
818
                                        "for taproot chan")
×
819
                }
×
820

821
                isInitiator := c.cfg.Channel.IsInitiator()
17✔
822

17✔
823
                // We'll compare the proposed total fee, to what we've proposed
17✔
824
                // during the negotiations. If it doesn't match any of our
17✔
825
                // prior offers, then we'll attempt to ratchet the fee closer
17✔
826
                // to our ideal fee.
17✔
827
                remoteProposedFee := msg.FeeSatoshis
17✔
828

17✔
829
                _, feeMatchesOffer := c.priorFeeOffers[remoteProposedFee]
17✔
830
                switch {
17✔
831
                // For taproot channels, since nonces are involved, we can't do
832
                // the existing co-op close negotiation process without going
833
                // to a fully round based model. Rather than do this, we'll
834
                // just accept the very first offer by the initiator.
835
                case isTaproot && !isInitiator:
5✔
836
                        chancloserLog.Infof("ChannelPoint(%v) accepting "+
5✔
837
                                "initiator fee of %v", c.chanPoint,
5✔
838
                                remoteProposedFee)
5✔
839

5✔
840
                        // To auto-accept the initiators proposal, we'll just
5✔
841
                        // send back a signature w/ the same offer. We don't
5✔
842
                        // send the message here, as we can drop down and
5✔
843
                        // finalize the closure and broadcast, then echo back
5✔
844
                        // to Alice the final signature.
5✔
845
                        _, err := c.proposeCloseSigned(remoteProposedFee)
5✔
846
                        if err != nil {
5✔
847
                                return noClosing, fmt.Errorf("unable to sign "+
×
848
                                        "new co op close offer: %w", err)
×
849
                        }
×
850

851
                // Otherwise, if we are the initiator, and we just sent a
852
                // signature for a taproot channel, then we'll ensure that the
853
                // fee rate matches up exactly.
854
                case isTaproot && isInitiator && !feeMatchesOffer:
1✔
855
                        return noClosing,
1✔
856
                                fmt.Errorf("fee rate for "+
1✔
857
                                        "taproot channels was not accepted: "+
1✔
858
                                        "sent %v, got %v",
1✔
859
                                        c.idealFeeSat, remoteProposedFee)
1✔
860

861
                // If we're the initiator of the taproot channel, and we had
862
                // our fee echo'd back, then it's all good, and we can proceed
863
                // with final broadcast.
864
                case isTaproot && isInitiator && feeMatchesOffer:
5✔
865
                        break
5✔
866

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

885
                        // With our new fee proposal calculated, we'll craft a
886
                        // new close signed signature to send to the other
887
                        // party so we can continue the fee negotiation
888
                        // process.
889
                        closeSigned, err := c.proposeCloseSigned(proposal)
9✔
890
                        if err != nil {
10✔
891
                                return noClosing, fmt.Errorf("unable to sign "+
1✔
892
                                        "new co op close offer: %w", err)
1✔
893
                        }
1✔
894

895
                        // If the compromise fee doesn't match what the peer
896
                        // proposed, then we'll return this latest close signed
897
                        // message so we can continue negotiation.
898
                        if proposal != remoteProposedFee {
12✔
899
                                chancloserLog.Debugf("ChannelPoint(%v): close "+
4✔
900
                                        "tx fee disagreement, continuing "+
4✔
901
                                        "negotiation", c.chanPoint)
4✔
902

4✔
903
                                return fn.Some(*closeSigned), nil
4✔
904
                        }
4✔
905
                }
906

907
                chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, "+
10✔
908
                        "ending negotiation", c.chanPoint, remoteProposedFee)
10✔
909

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

933
                        muSession := c.cfg.MusigSession
6✔
934
                        localSig, remoteSig, closeOpts, err = muSession.CombineClosingOpts( //nolint:ll
6✔
935
                                localWireSig, remoteWireSig,
6✔
936
                        )
6✔
937
                        if err != nil {
6✔
938
                                return noClosing, err
×
939
                        }
×
940
                } else {
8✔
941
                        localSig, err = matchingSig.Signature.ToSignature()
8✔
942
                        if err != nil {
8✔
943
                                return noClosing, err
×
944
                        }
×
945
                        remoteSig, err = msg.Signature.ToSignature()
8✔
946
                        if err != nil {
8✔
947
                                return noClosing, err
×
948
                        }
×
949
                }
950

951
                // Before we complete the cooperative close, we'll see if we
952
                // have any extra aux options.
953
                c.auxOutputs, err = c.auxCloseOutputs(remoteProposedFee)
10✔
954
                if err != nil {
10✔
955
                        return noClosing, err
×
956
                }
×
957
                c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
10✔
958
                        closeOpts = append(
×
959
                                closeOpts, lnwallet.WithExtraCloseOutputs(
×
960
                                        outs.ExtraCloseOutputs,
×
961
                                ),
×
962
                        )
×
963
                        closeOpts = append(
×
964
                                closeOpts, lnwallet.WithCustomCoopSort(
×
965
                                        outs.CustomSort,
×
966
                                ),
×
967
                        )
×
968
                })
×
969

970
                closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
10✔
971
                        localSig, remoteSig, c.localDeliveryScript,
10✔
972
                        c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
10✔
973
                )
10✔
974
                if err != nil {
10✔
975
                        return noClosing, err
×
976
                }
×
977
                c.closingTx = closeTx
10✔
978

10✔
979
                // If there's an aux chan closer, then we'll finalize with it
10✔
980
                // before we write to disk.
10✔
981
                err = fn.MapOptionZ(
10✔
982
                        c.cfg.AuxCloser, func(aux AuxChanCloser) error {
10✔
983
                                channel := c.cfg.Channel
×
984
                                //nolint:ll
×
985
                                req := AuxShutdownReq{
×
986
                                        ChanPoint:   c.chanPoint,
×
987
                                        ShortChanID: c.cfg.Channel.ShortChanID(),
×
988
                                        InternalKey: c.localInternalKey,
×
989
                                        Initiator:   channel.IsInitiator(),
×
990
                                        CommitBlob:  channel.LocalCommitmentBlob(),
×
991
                                        FundingBlob: channel.FundingBlob(),
×
992
                                }
×
993
                                desc := AuxCloseDesc{
×
994
                                        AuxShutdownReq:    req,
×
995
                                        LocalCloseOutput:  c.localCloseOutput,
×
996
                                        RemoteCloseOutput: c.remoteCloseOutput,
×
997
                                }
×
998

×
999
                                return aux.FinalizeClose(desc, closeTx)
×
1000
                        },
×
1001
                )
1002
                if err != nil {
10✔
1003
                        return noClosing, err
×
1004
                }
×
1005

1006
                // Before publishing the closing tx, we persist it to the
1007
                // database, such that it can be republished if something goes
1008
                // wrong.
1009
                err = c.cfg.Channel.MarkCoopBroadcasted(
10✔
1010
                        closeTx, c.closer,
10✔
1011
                )
10✔
1012
                if err != nil {
11✔
1013
                        return noClosing, err
1✔
1014
                }
1✔
1015

1016
                // With the closing transaction crafted, we'll now broadcast it
1017
                // to the network.
1018
                chancloserLog.Infof("Broadcasting cooperative close tx: %v",
10✔
1019
                        lnutils.SpewLogClosure(closeTx))
10✔
1020

10✔
1021
                // Create a close channel label.
10✔
1022
                chanID := c.cfg.Channel.ShortChanID()
10✔
1023
                closeLabel := labels.MakeLabel(
10✔
1024
                        labels.LabelTypeChannelClose, &chanID,
10✔
1025
                )
10✔
1026

10✔
1027
                if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
10✔
1028
                        return noClosing, err
×
1029
                }
×
1030

1031
                // Finally, we'll transition to the closeFinished state, and
1032
                // also return the final close signed message we sent.
1033
                // Additionally, we return true for the second argument to
1034
                // indicate we're finished with the channel closing
1035
                // negotiation.
1036
                c.state = closeFinished
10✔
1037
                matchingOffer := c.priorFeeOffers[remoteProposedFee]
10✔
1038

10✔
1039
                return fn.Some(*matchingOffer), nil
10✔
1040

1041
        // If we received a message while in the closeFinished state, then this
1042
        // should only be the remote party echoing the last ClosingSigned
1043
        // message that we agreed on.
1044
        case closeFinished:
4✔
1045

4✔
1046
                // There's no more to do as both sides should have already
4✔
1047
                // broadcast the closing transaction at this state.
4✔
1048
                return noClosing, nil
4✔
1049

1050
        default:
×
1051
                return noClosing, ErrInvalidState
×
1052
        }
1053
}
1054

1055
// auxCloseOutputs returns any additional outputs that should be used when
1056
// closing the channel.
1057
func (c *ChanCloser) auxCloseOutputs(
1058
        closeFee btcutil.Amount) (fn.Option[AuxCloseOutputs], error) {
22✔
1059

22✔
1060
        var closeOuts fn.Option[AuxCloseOutputs]
22✔
1061
        err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error {
22✔
1062
                req := AuxShutdownReq{
×
1063
                        ChanPoint:   c.chanPoint,
×
1064
                        ShortChanID: c.cfg.Channel.ShortChanID(),
×
1065
                        InternalKey: c.localInternalKey,
×
1066
                        Initiator:   c.cfg.Channel.IsInitiator(),
×
1067
                        CommitBlob:  c.cfg.Channel.LocalCommitmentBlob(),
×
1068
                        FundingBlob: c.cfg.Channel.FundingBlob(),
×
1069
                }
×
1070
                outs, err := aux.AuxCloseOutputs(AuxCloseDesc{
×
1071
                        AuxShutdownReq:    req,
×
1072
                        CloseFee:          closeFee,
×
1073
                        CommitFee:         c.cfg.Channel.CommitFee(),
×
1074
                        LocalCloseOutput:  c.localCloseOutput,
×
1075
                        RemoteCloseOutput: c.remoteCloseOutput,
×
1076
                })
×
1077
                if err != nil {
×
1078
                        return err
×
1079
                }
×
1080

1081
                closeOuts = outs
×
1082

×
1083
                return nil
×
1084
        })
1085
        if err != nil {
22✔
1086
                return closeOuts, err
×
1087
        }
×
1088

1089
        return closeOuts, nil
22✔
1090
}
1091

1092
// proposeCloseSigned attempts to propose a new signature for the closing
1093
// transaction for a channel based on the prior fee negotiations and our
1094
// current compromise fee.
1095
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
1096
        *lnwire.ClosingSigned, error) {
16✔
1097

16✔
1098
        var (
16✔
1099
                closeOpts []lnwallet.ChanCloseOpt
16✔
1100
                err       error
16✔
1101
        )
16✔
1102

16✔
1103
        // If this is a taproot channel, then we'll include the musig session
16✔
1104
        // generated for the next co-op close negotiation round.
16✔
1105
        if c.cfg.Channel.ChanType().IsTaproot() {
22✔
1106
                closeOpts, err = c.cfg.MusigSession.ProposalClosingOpts()
6✔
1107
                if err != nil {
6✔
1108
                        return nil, err
×
1109
                }
×
1110
        }
1111

1112
        // We'll also now see if the aux chan closer has any additional options
1113
        // for the closing purpose.
1114
        c.auxOutputs, err = c.auxCloseOutputs(fee)
16✔
1115
        if err != nil {
16✔
1116
                return nil, err
×
1117
        }
×
1118
        c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
16✔
1119
                closeOpts = append(
×
1120
                        closeOpts, lnwallet.WithExtraCloseOutputs(
×
1121
                                outs.ExtraCloseOutputs,
×
1122
                        ),
×
1123
                )
×
1124
                closeOpts = append(
×
1125
                        closeOpts, lnwallet.WithCustomCoopSort(
×
1126
                                outs.CustomSort,
×
1127
                        ),
×
1128
                )
×
1129
        })
×
1130

1131
        // With all our options added, we'll attempt to co-op close now.
1132
        rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
16✔
1133
                fee, c.localDeliveryScript, c.remoteDeliveryScript,
16✔
1134
                closeOpts...,
16✔
1135
        )
16✔
1136
        if err != nil {
16✔
1137
                return nil, err
×
1138
        }
×
1139

1140
        // We'll note our last signature and proposed fee so when the remote
1141
        // party responds we'll be able to decide if we've agreed on fees or
1142
        // not.
1143
        var (
16✔
1144
                parsedSig  lnwire.Sig
16✔
1145
                partialSig *lnwire.PartialSigWithNonce
16✔
1146
        )
16✔
1147
        if c.cfg.Channel.ChanType().IsTaproot() {
22✔
1148
                musig, ok := rawSig.(*lnwallet.MusigPartialSig)
6✔
1149
                if !ok {
6✔
1150
                        return nil, fmt.Errorf("expected MusigPartialSig, "+
×
1151
                                "got %T", rawSig)
×
1152
                }
×
1153

1154
                partialSig = musig.ToWireSig()
6✔
1155
        } else {
14✔
1156
                parsedSig, err = lnwire.NewSigFromSignature(rawSig)
14✔
1157
                if err != nil {
15✔
1158
                        return nil, err
1✔
1159
                }
1✔
1160
        }
1161

1162
        c.lastFeeProposal = fee
15✔
1163

15✔
1164
        chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+
15✔
1165
                "close chan", c.chanPoint, int64(fee))
15✔
1166

15✔
1167
        // We'll assemble a ClosingSigned message using this information and
15✔
1168
        // return it to the caller so we can kick off the final stage of the
15✔
1169
        // channel closure process.
15✔
1170
        closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig)
15✔
1171

15✔
1172
        // For musig2 channels, the main sig is blank, and instead we'll send
15✔
1173
        // over a partial signature which'll be combined once our offer is
15✔
1174
        // accepted.
15✔
1175
        if partialSig != nil {
21✔
1176
                closeSignedMsg.PartialSig = lnwire.SomePartialSig(
6✔
1177
                        partialSig.PartialSig,
6✔
1178
                )
6✔
1179
        }
6✔
1180

1181
        // We'll also save this close signed, in the case that the remote party
1182
        // accepts our offer. This way, we don't have to re-sign.
1183
        c.priorFeeOffers[fee] = closeSignedMsg
15✔
1184

15✔
1185
        return closeSignedMsg, nil
15✔
1186
}
1187

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

1200
        // If our offer is greater than theirs, then we'll accept their offer if
1201
        // it's no more than 30% *less* than our current offer.
1202
        acceptableRange := localFee - ((localFee * 3) / 10)
×
1203
        return remoteFee >= acceptableRange
×
1204
}
1205

1206
// ratchetFee is our step function used to inch our fee closer to something
1207
// that both sides can agree on. If up is true, then we'll attempt to increase
1208
// our offered fee. Otherwise, if up is false, then we'll attempt to decrease
1209
// our offered fee.
1210
func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount {
6✔
1211
        // If we need to ratchet up, then we'll increase our fee by 10%.
6✔
1212
        if up {
12✔
1213
                return fee + ((fee * 1) / 10)
6✔
1214
        }
6✔
1215

1216
        // Otherwise, we'll *decrease* our fee by 10%.
1217
        return fee - ((fee * 1) / 10)
×
1218
}
1219

1220
// calcCompromiseFee performs the current fee negotiation algorithm, taking
1221
// into consideration our ideal fee based on current fee environment, the fee
1222
// we last proposed (if any), and the fee proposed by the peer.
1223
func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
1224
        remoteFee btcutil.Amount) btcutil.Amount {
10✔
1225

10✔
1226
        // TODO(roasbeef): take in number of rounds as well?
10✔
1227

10✔
1228
        chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, "+
10✔
1229
                "ideal=%v, last_sent=%v, remote_offer=%v", chanPoint,
10✔
1230
                int64(ourIdealFee), int64(lastSentFee), int64(remoteFee))
10✔
1231

10✔
1232
        // Otherwise, we'll need to attempt to make a fee compromise if this is
10✔
1233
        // the second round, and neither side has agreed on fees.
10✔
1234
        switch {
10✔
1235
        // If their proposed fee is identical to our ideal fee, then we'll go
1236
        // with that as we can short circuit the fee negotiation. Similarly, if
1237
        // we haven't sent an offer yet, we'll default to our ideal fee.
1238
        case ourIdealFee == remoteFee || lastSentFee == 0:
4✔
1239
                return ourIdealFee
4✔
1240

1241
        // If the last fee we sent, is equal to the fee the remote party is
1242
        // offering, then we can simply return this fee as the negotiation is
1243
        // over.
1244
        case remoteFee == lastSentFee:
×
1245
                return lastSentFee
×
1246

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

×
1258
                        return remoteFee
×
1259
                }
×
1260

1261
                // Otherwise, we'll ratchet the fee *down* using our current
1262
                // algorithm.
1263
                return ratchetFee(lastSentFee, false)
×
1264

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

×
1276
                        return remoteFee
×
1277
                }
×
1278

1279
                // Otherwise, we'll ratchet the fee up using our current
1280
                // algorithm.
1281
                return ratchetFee(lastSentFee, true)
6✔
1282

1283
        default:
×
1284
                // TODO(roasbeef): fail if their fee isn't in expected range
×
1285
                return remoteFee
×
1286
        }
1287
}
1288

1289
// ParseUpfrontShutdownAddress attempts to parse an upfront shutdown address.
1290
// If the address is empty, it returns nil. If it successfully decoded the
1291
// address, it returns a script that pays out to the address.
1292
func ParseUpfrontShutdownAddress(address string,
1293
        params *chaincfg.Params) (lnwire.DeliveryAddress, error) {
19✔
1294

19✔
1295
        if len(address) == 0 {
30✔
1296
                return nil, nil
11✔
1297
        }
11✔
1298

1299
        addr, err := btcutil.DecodeAddress(
12✔
1300
                address, params,
12✔
1301
        )
12✔
1302
        if err != nil {
14✔
1303
                return nil, fmt.Errorf("invalid address: %w", err)
2✔
1304
        }
2✔
1305

1306
        if !addr.IsForNet(params) {
11✔
1307
                return nil, fmt.Errorf("invalid address: %v is not a %s "+
1✔
1308
                        "address", addr, params.Name)
1✔
1309
        }
1✔
1310

1311
        return txscript.PayToAddrScript(addr)
9✔
1312
}
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