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

lightningnetwork / lnd / 16990665124

15 Aug 2025 01:10PM UTC coverage: 66.74% (-0.03%) from 66.765%
16990665124

Pull #9455

github

web-flow
Merge 035fac41d into fb1adfc21
Pull Request #9455: [1/2] discovery+lnwire: add support for DNS host name in NodeAnnouncement msg

116 of 188 new or added lines in 8 files covered. (61.7%)

110 existing lines in 23 files now uncovered.

136011 of 203791 relevant lines covered (66.74%)

21482.89 hits per line

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

74.79
/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 {
19✔
260

19✔
261
        var weightEstimator input.TxWeightEstimator
19✔
262

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

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

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

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

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

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

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

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

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

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

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

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

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

412
                shutdown.ShutdownNonce = lnwire.SomeShutdownNonce(
5✔
413
                        firstClosingNonce.PubNonce,
5✔
414
                )
5✔
415

5✔
416
                chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
5✔
417
                        spew.Sdump(firstClosingNonce.PubNonce))
5✔
418
        }
419

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

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

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

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

14✔
453
        return shutdown, nil
14✔
454
}
455

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

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

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

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

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

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

495
        return c.closingTx, nil
14✔
496
}
497

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

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

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

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

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

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

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

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

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

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

2✔
574
                return ErrUpfrontShutdownScriptMismatch
2✔
575
        }
2✔
576

577
        return nil
5✔
578
}
579

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

10✔
588
        noShutdown := fn.None[lnwire.Shutdown]()
10✔
589

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

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

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

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

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

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

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

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

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

7✔
674
                return fn.Some(*localShutdown), err
7✔
675

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

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

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

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

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

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

6✔
715
                return noShutdown, nil
6✔
716

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

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

10✔
734
        noClosingSigned := fn.None[lnwire.ClosingSigned]()
10✔
735

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

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

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

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

771
                        return res, err
4✔
772
                }
773

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

783
                return fn.Some(*closingSigned), nil
9✔
784

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

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

17✔
796
        noClosing := fn.None[lnwire.ClosingSigned]()
17✔
797

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

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

816
                isInitiator := c.cfg.Channel.IsInitiator()
16✔
817

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

9✔
1034
                return fn.Some(*matchingOffer), nil
9✔
1035

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

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

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

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

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

1076
                closeOuts = outs
×
1077

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

1084
        return closeOuts, nil
21✔
1085
}
1086

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

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

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

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

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

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

1149
                partialSig = musig.ToWireSig()
5✔
1150
        } else {
13✔
1151
                parsedSig, err = lnwire.NewSigFromSignature(rawSig)
13✔
1152
                if err != nil {
14✔
1153
                        return nil, err
1✔
1154
                }
1✔
1155
        }
1156

1157
        c.lastFeeProposal = fee
14✔
1158

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

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

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

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

14✔
1180
        return closeSignedMsg, nil
14✔
1181
}
1182

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

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

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

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

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

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

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

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

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

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

×
1253
                        return remoteFee
×
1254
                }
×
1255

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

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

×
1271
                        return remoteFee
×
1272
                }
×
1273

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

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

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

18✔
1290
        if len(address) == 0 {
28✔
1291
                return nil, nil
10✔
1292
        }
10✔
1293

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

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

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