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

lightningnetwork / lnd / 17830307614

18 Sep 2025 01:29PM UTC coverage: 54.617% (-12.0%) from 66.637%
17830307614

Pull #10200

github

web-flow
Merge 181a0a7bc into b34fc964b
Pull Request #10200: github: change to form-based issue template

109249 of 200028 relevant lines covered (54.62%)

21896.43 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/lightningnetwork/lnd/channeldb"
14
        "github.com/lightningnetwork/lnd/fn/v2"
15
        "github.com/lightningnetwork/lnd/htlcswitch"
16
        "github.com/lightningnetwork/lnd/input"
17
        "github.com/lightningnetwork/lnd/labels"
18
        "github.com/lightningnetwork/lnd/lntypes"
19
        "github.com/lightningnetwork/lnd/lnutils"
20
        "github.com/lightningnetwork/lnd/lnwallet"
21
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
22
        "github.com/lightningnetwork/lnd/lnwire"
23
)
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

16✔
260
        var weightEstimator input.TxWeightEstimator
16✔
261

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

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

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

16✔
281
        return idealFeeRate.FeeForWeight(totalWeight)
16✔
282
}
283

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

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

16✔
297
        return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
16✔
298
}
16✔
299

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

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

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

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

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

362
        // TODO(ziggie): Make sure the ideal fee is not higher than the max fee.
363
        // Either error out or cap the ideal fee at the max fee.
364

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

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

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

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

397
                return nil
×
398
        })
399
        if err != nil {
11✔
400
                return nil, err
×
401
        }
×
402

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

411
                shutdown.ShutdownNonce = lnwire.SomeShutdownNonce(
2✔
412
                        firstClosingNonce.PubNonce,
2✔
413
                )
2✔
414

2✔
415
                chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
2✔
416
                        lnutils.SpewLogClosure(firstClosingNonce.PubNonce))
2✔
417
        }
418

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

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

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

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

11✔
452
        return shutdown, nil
11✔
453
}
454

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

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

7✔
468
        shutdownMsg, err := c.initChanShutdown()
7✔
469
        if err != nil {
7✔
470
                return nil, err
×
471
        }
×
472

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

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

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

494
        return c.closingTx, nil
11✔
495
}
496

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

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

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

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

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

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

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

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

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

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

2✔
573
                return ErrUpfrontShutdownScriptMismatch
2✔
574
        }
2✔
575

576
        return nil
2✔
577
}
578

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

7✔
587
        noShutdown := fn.None[lnwire.Shutdown]()
7✔
588

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

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

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

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

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

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

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

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

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

4✔
673
                return fn.Some(*localShutdown), err
4✔
674

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

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

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

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

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

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

3✔
714
                return noShutdown, nil
3✔
715

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

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

7✔
733
        noClosingSigned := fn.None[lnwire.ClosingSigned]()
7✔
734

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

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

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

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

770
                        return res, err
1✔
771
                }
772

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

782
                return fn.Some(*closingSigned), nil
6✔
783

784
        default:
×
785
                return noClosingSigned, ErrInvalidState
×
786
        }
787
}
788

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

14✔
795
        noClosing := fn.None[lnwire.ClosingSigned]()
14✔
796

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

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

815
                isInitiator := c.cfg.Channel.IsInitiator()
13✔
816

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

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

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

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

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

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

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

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

4✔
897
                                return fn.Some(*closeSigned), nil
4✔
898
                        }
4✔
899
                }
900

901
                chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, "+
6✔
902
                        "ending negotiation", c.chanPoint, remoteProposedFee)
6✔
903

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

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

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

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

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

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

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

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

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

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

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

6✔
1033
                return fn.Some(*matchingOffer), nil
6✔
1034

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

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

1044
        default:
×
1045
                return noClosing, ErrInvalidState
×
1046
        }
1047
}
1048

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

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

1075
                closeOuts = outs
×
1076

×
1077
                return nil
×
1078
        })
1079
        if err != nil {
18✔
1080
                return closeOuts, err
×
1081
        }
×
1082

1083
        return closeOuts, nil
18✔
1084
}
1085

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

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

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

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

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

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

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

1156
        c.lastFeeProposal = fee
11✔
1157

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

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

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

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

11✔
1179
        return closeSignedMsg, nil
11✔
1180
}
1181

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

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

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

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

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

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

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

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

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

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

×
1252
                        return remoteFee
×
1253
                }
×
1254

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

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

×
1270
                        return remoteFee
×
1271
                }
×
1272

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

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

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

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

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

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

1305
        return txscript.PayToAddrScript(addr)
5✔
1306
}
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