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

lightningnetwork / lnd / 12986279612

27 Jan 2025 09:51AM UTC coverage: 57.652% (-1.1%) from 58.788%
12986279612

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19578 existing lines in 256 files now uncovered.

103448 of 179434 relevant lines covered (57.65%)

24884.58 hits per line

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

73.15
/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 {
16✔
260

16✔
261
        var weightEstimator input.TxWeightEstimator
16✔
262

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

529
// AuxOutputs returns optional extra outputs.
530
func (c *ChanCloser) AuxOutputs() fn.Option[AuxCloseOutputs] {
4✔
531
        return c.auxOutputs
4✔
532
}
4✔
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 {
32✔
544

32✔
545
        // Either way, we'll make sure that the script passed meets our
32✔
546
        // standards. The upfrontScript should have already been checked at an
32✔
547
        // earlier stage, but we'll repeat the check here for defense in depth.
32✔
548
        if len(upfrontScript) != 0 {
36✔
549
                if !lnwallet.ValidateUpfrontShutdown(upfrontScript, netParams) {
4✔
550
                        return ErrInvalidShutdownScript
×
551
                }
×
552
        }
553
        if len(peerScript) != 0 {
61✔
554
                if !lnwallet.ValidateUpfrontShutdown(peerScript, netParams) {
30✔
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 {
58✔
562
                return nil
27✔
563
        }
27✔
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) {
6✔
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
2✔
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) {
7✔
584

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

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

7✔
598
        switch c.state {
7✔
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:
4✔
603
                // As we're the responder to this shutdown (the other party
4✔
604
                // wants to close), we'll check if this is a frozen channel or
4✔
605
                // not. If the channel is frozen and we were not also the
4✔
606
                // initiator of the channel opening, then we'll deny their close
4✔
607
                // attempt.
4✔
608
                chanInitiator := c.cfg.Channel.IsInitiator()
4✔
609
                if !chanInitiator {
5✔
610
                        absoluteThawHeight, err :=
1✔
611
                                c.cfg.Channel.AbsoluteThawHeight()
1✔
612
                        if err != nil {
1✔
613
                                return noShutdown, err
×
614
                        }
×
615
                        if c.negotiationHeight < absoluteThawHeight {
1✔
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(
4✔
627
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
4✔
628
                        msg.Address, c.cfg.ChainParams,
4✔
629
                ); err != nil {
4✔
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
4✔
638

4✔
639
                // We'll generate a shutdown message of our own to send across
4✔
640
                // the wire.
4✔
641
                localShutdown, err := c.initChanShutdown()
4✔
642
                if err != nil {
4✔
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() {
5✔
650
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
1✔
651
                                errNoShutdownNonce,
1✔
652
                        )
1✔
653
                        if err != nil {
1✔
654
                                return noShutdown, err
×
655
                        }
×
656

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

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

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

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

673
        case closeShutdownInitiated:
3✔
674
                // If the remote node opened the channel with option upfront
3✔
675
                // shutdown script, check that the script they provided matches.
3✔
676
                if err := validateShutdownScript(
3✔
677
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
3✔
678
                        msg.Address, c.cfg.ChainParams,
3✔
679
                ); err != nil {
3✔
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
3✔
687

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

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

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

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

3✔
712
                return noShutdown, nil
3✔
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) {
7✔
730

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

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

7✔
739
                // Before continuing, mark the channel as cooperatively closed
7✔
740
                // with a nil txn. Even though we haven't negotiated the final
7✔
741
                // txn, this guarantees that our listchannels rpc will be
7✔
742
                // externally consistent, and reflect that the channel is being
7✔
743
                // shutdown by the time the closing request returns.
7✔
744
                err := c.cfg.Channel.MarkCoopBroadcasted(
7✔
745
                        nil, c.closer,
7✔
746
                )
7✔
747
                if err != nil {
7✔
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
7✔
755

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

768
                        return res, err
1✔
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)
6✔
774
                if err != nil {
6✔
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
6✔
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) {
14✔
792

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

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

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

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

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

13✔
821
                _, feeMatchesOffer := c.priorFeeOffers[remoteProposedFee]
13✔
822
                switch {
13✔
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:
1✔
828
                        chancloserLog.Infof("ChannelPoint(%v) accepting "+
1✔
829
                                "initiator fee of %v", c.chanPoint,
1✔
830
                                remoteProposedFee)
1✔
831

1✔
832
                        // To auto-accept the initiators proposal, we'll just
1✔
833
                        // send back a signature w/ the same offer. We don't
1✔
834
                        // send the message here, as we can drop down and
1✔
835
                        // finalize the closure and broadcast, then echo back
1✔
836
                        // to Alice the final signature.
1✔
837
                        _, err := c.proposeCloseSigned(remoteProposedFee)
1✔
838
                        if err != nil {
1✔
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:
1✔
857
                        break
1✔
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:
6✔
863
                        // We'll now attempt to ratchet towards a fee deemed
6✔
864
                        // acceptable by both parties, factoring in our ideal
6✔
865
                        // fee rate, and the last proposed fee by both sides.
6✔
866
                        proposal := calcCompromiseFee(
6✔
867
                                c.chanPoint, c.idealFeeSat, c.lastFeeProposal,
6✔
868
                                remoteProposedFee,
6✔
869
                        )
6✔
870
                        if c.cfg.Channel.IsInitiator() && proposal > c.maxFee {
7✔
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)
5✔
882
                        if err != nil {
6✔
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 {
8✔
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, "+
6✔
900
                        "ending negotiation", c.chanPoint, remoteProposedFee)
6✔
901

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

925
                        muSession := c.cfg.MusigSession
2✔
926
                        localSig, remoteSig, closeOpts, err = muSession.CombineClosingOpts( //nolint:ll
2✔
927
                                localWireSig, remoteWireSig,
2✔
928
                        )
2✔
929
                        if err != nil {
2✔
930
                                return noClosing, err
×
931
                        }
×
932
                } else {
4✔
933
                        localSig, err = matchingSig.Signature.ToSignature()
4✔
934
                        if err != nil {
4✔
935
                                return noClosing, err
×
936
                        }
×
937
                        remoteSig, err = msg.Signature.ToSignature()
4✔
938
                        if err != nil {
4✔
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)
6✔
946
                if err != nil {
6✔
947
                        return noClosing, err
×
948
                }
×
949
                c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
6✔
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(
6✔
963
                        localSig, remoteSig, c.localDeliveryScript,
6✔
964
                        c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
6✔
965
                )
6✔
966
                if err != nil {
6✔
967
                        return noClosing, err
×
968
                }
×
969
                c.closingTx = closeTx
6✔
970

6✔
971
                // If there's an aux chan closer, then we'll finalize with it
6✔
972
                // before we write to disk.
6✔
973
                err = fn.MapOptionZ(
6✔
974
                        c.cfg.AuxCloser, func(aux AuxChanCloser) error {
6✔
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 {
6✔
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(
6✔
1002
                        closeTx, c.closer,
6✔
1003
                )
6✔
1004
                if err != nil {
6✔
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",
6✔
1011
                        lnutils.SpewLogClosure(closeTx))
6✔
1012

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

6✔
1019
                if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
6✔
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
6✔
1029
                matchingOffer := c.priorFeeOffers[remoteProposedFee]
6✔
1030

6✔
1031
                return fn.Some(*matchingOffer), nil
6✔
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:
1✔
1037

1✔
1038
                // There's no more to do as both sides should have already
1✔
1039
                // broadcast the closing transaction at this state.
1✔
1040
                return noClosing, nil
1✔
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) {
18✔
1051

18✔
1052
        var closeOuts fn.Option[AuxCloseOutputs]
18✔
1053
        err := fn.MapOptionZ(c.cfg.AuxCloser, func(aux AuxChanCloser) error {
18✔
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 {
18✔
1078
                return closeOuts, err
×
1079
        }
×
1080

1081
        return closeOuts, nil
18✔
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) {
12✔
1089

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

12✔
1095
        // If this is a taproot channel, then we'll include the musig session
12✔
1096
        // generated for the next co-op close negotiation round.
12✔
1097
        if c.cfg.Channel.ChanType().IsTaproot() {
14✔
1098
                closeOpts, err = c.cfg.MusigSession.ProposalClosingOpts()
2✔
1099
                if err != nil {
2✔
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)
12✔
1107
        if err != nil {
12✔
1108
                return nil, err
×
1109
        }
×
1110
        c.auxOutputs.WhenSome(func(outs AuxCloseOutputs) {
12✔
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(
12✔
1125
                fee, c.localDeliveryScript, c.remoteDeliveryScript,
12✔
1126
                closeOpts...,
12✔
1127
        )
12✔
1128
        if err != nil {
12✔
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 (
12✔
1136
                parsedSig  lnwire.Sig
12✔
1137
                partialSig *lnwire.PartialSigWithNonce
12✔
1138
        )
12✔
1139
        if c.cfg.Channel.ChanType().IsTaproot() {
14✔
1140
                musig, ok := rawSig.(*lnwallet.MusigPartialSig)
2✔
1141
                if !ok {
2✔
1142
                        return nil, fmt.Errorf("expected MusigPartialSig, "+
×
1143
                                "got %T", rawSig)
×
1144
                }
×
1145

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

1154
        c.lastFeeProposal = fee
11✔
1155

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

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

11✔
1164
        // For musig2 channels, the main sig is blank, and instead we'll send
11✔
1165
        // over a partial signature which'll be combined once our offer is
11✔
1166
        // accepted.
11✔
1167
        if partialSig != nil {
13✔
1168
                closeSignedMsg.PartialSig = lnwire.SomePartialSig(
2✔
1169
                        partialSig.PartialSig,
2✔
1170
                )
2✔
1171
        }
2✔
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
11✔
1176

11✔
1177
        return closeSignedMsg, nil
11✔
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 {
6✔
1217

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

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

6✔
1224
        // Otherwise, we'll need to attempt to make a fee compromise if this is
6✔
1225
        // the second round, and neither side has agreed on fees.
6✔
1226
        switch {
6✔
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.
UNCOV
1230
        case ourIdealFee == remoteFee || lastSentFee == 0:
×
UNCOV
1231
                return ourIdealFee
×
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) {
15✔
1286

15✔
1287
        if len(address) == 0 {
22✔
1288
                return nil, nil
7✔
1289
        }
7✔
1290

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

1298
        if !addr.IsForNet(params) {
7✔
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)
5✔
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