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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

107
// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
108
// duties.
109
type ChanCloseCfg struct {
110
        // Channel is the channel that should be closed.
111
        Channel Channel
112

113
        // MusigSession is used to handle generating musig2 nonces, and also
114
        // creating the proper set of closing options for taproot channels.
115
        MusigSession MusigSession
116

117
        // BroadcastTx broadcasts the passed transaction to the network.
118
        BroadcastTx func(*wire.MsgTx, string) error
119

120
        // DisableChannel disables a channel, resulting in it not being able to
121
        // forward payments.
122
        DisableChannel func(wire.OutPoint) error
123

124
        // Disconnect will disconnect from the remote peer in this close.
125
        Disconnect func() error
126

127
        // MaxFee, is non-zero represents the highest fee that the initiator is
128
        // willing to pay to close the channel.
129
        MaxFee chainfee.SatPerKWeight
130

131
        // ChainParams holds the parameters of the chain that we're active on.
132
        ChainParams *chaincfg.Params
133

134
        // Quit is a channel that should be sent upon in the occasion the state
135
        // machine should cease all progress and shutdown.
136
        Quit chan struct{}
137

138
        // FeeEstimator is used to estimate the absolute starting co-op close
139
        // fee.
140
        FeeEstimator CoopFeeEstimator
141
}
142

143
// ChanCloser is a state machine that handles the cooperative channel closure
144
// procedure. This includes shutting down a channel, marking it ineligible for
145
// routing HTLC's, negotiating fees with the remote party, and finally
146
// broadcasting the fully signed closure transaction to the network.
147
type ChanCloser struct {
148
        // state is the current state of the state machine.
149
        state closeState
150

151
        // cfg holds the configuration for this ChanCloser instance.
152
        cfg ChanCloseCfg
153

154
        // chanPoint is the full channel point of the target channel.
155
        chanPoint wire.OutPoint
156

157
        // cid is the full channel ID of the target channel.
158
        cid lnwire.ChannelID
159

160
        // negotiationHeight is the height that the fee negotiation begun at.
161
        negotiationHeight uint32
162

163
        // closingTx is the final, fully signed closing transaction. This will
164
        // only be populated once the state machine shifts to the closeFinished
165
        // state.
166
        closingTx *wire.MsgTx
167

168
        // idealFeeSat is the ideal fee that the state machine should initially
169
        // offer when starting negotiation. This will be used as a baseline.
170
        idealFeeSat btcutil.Amount
171

172
        // maxFee is the highest fee the initiator is willing to pay to close
173
        // out the channel. This is either a use specified value, or a default
174
        // multiplier based of the initial starting ideal fee.
175
        maxFee btcutil.Amount
176

177
        // idealFeeRate is our ideal fee rate.
178
        idealFeeRate chainfee.SatPerKWeight
179

180
        // lastFeeProposal is the last fee that we proposed to the remote party.
181
        // We'll use this as a pivot point to ratchet our next offer up, down,
182
        // or simply accept the remote party's prior offer.
183
        lastFeeProposal btcutil.Amount
184

185
        // priorFeeOffers is a map that keeps track of all the proposed fees
186
        // that we've offered during the fee negotiation. We use this map to cut
187
        // the negotiation early if the remote party ever sends an offer that
188
        // we've sent in the past. Once negotiation terminates, we can extract
189
        // the prior signature of our accepted offer from this map.
190
        //
191
        // TODO(roasbeef): need to ensure if they broadcast w/ any of our prior
192
        // sigs, we are aware of
193
        priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned
194

195
        // closeReq is the initial closing request. This will only be populated
196
        // if we're the initiator of this closing negotiation.
197
        //
198
        // TODO(roasbeef): abstract away
199
        closeReq *htlcswitch.ChanClose
200

201
        // localDeliveryScript is the script that we'll send our settled channel
202
        // funds to.
203
        localDeliveryScript []byte
204

205
        // remoteDeliveryScript is the script that we'll send the remote party's
206
        // settled channel funds to.
207
        remoteDeliveryScript []byte
208

209
        // locallyInitiated is true if we initiated the channel close.
210
        locallyInitiated bool
211

212
        // cachedClosingSigned is a cached copy of a received ClosingSigned that
213
        // we use to handle a specific race condition caused by the independent
214
        // message processing queues.
215
        cachedClosingSigned fn.Option[lnwire.ClosingSigned]
216
}
217

218
// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
219
// delivery scripts of both parties and our ideal fee rate.
220
func calcCoopCloseFee(chanType channeldb.ChannelType,
221
        localOutput, remoteOutput *wire.TxOut,
222
        idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
3✔
223

3✔
224
        var weightEstimator input.TxWeightEstimator
3✔
225

3✔
226
        if chanType.IsTaproot() {
3✔
227
                weightEstimator.AddWitnessInput(
×
228
                        input.TaprootSignatureWitnessSize,
×
229
                )
×
230
        } else {
3✔
231
                weightEstimator.AddWitnessInput(input.MultiSigWitnessSize)
3✔
232
        }
3✔
233

234
        // One of these outputs might be dust, so we'll skip adding it to our
235
        // mock transaction, so the fees are more accurate.
236
        if localOutput != nil {
6✔
237
                weightEstimator.AddTxOutput(localOutput)
3✔
238
        }
3✔
239
        if remoteOutput != nil {
6✔
240
                weightEstimator.AddTxOutput(remoteOutput)
3✔
241
        }
3✔
242

243
        totalWeight := weightEstimator.Weight()
3✔
244

3✔
245
        return idealFeeRate.FeeForWeight(totalWeight)
3✔
246
}
247

248
// SimpleCoopFeeEstimator is the default co-op close fee estimator. It assumes
249
// a normal segwit v0 channel, and that no outputs on the closing transaction
250
// are dust.
251
type SimpleCoopFeeEstimator struct {
252
}
253

254
// EstimateFee estimates an _absolute_ fee for a co-op close transaction given
255
// the local+remote tx outs (for the co-op close transaction), channel type,
256
// and ideal fee rate.
257
func (d *SimpleCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
258
        localTxOut, remoteTxOut *wire.TxOut,
259
        idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
3✔
260

3✔
261
        return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
3✔
262
}
3✔
263

264
// NewChanCloser creates a new instance of the channel closure given the passed
265
// configuration, and delivery+fee preference. The final argument should only
266
// be populated iff, we're the initiator of this closing request.
267
func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
268
        idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
269
        closeReq *htlcswitch.ChanClose, locallyInitiated bool) *ChanCloser {
3✔
270

3✔
271
        chanPoint := cfg.Channel.ChannelPoint()
3✔
272
        cid := lnwire.NewChanIDFromOutPoint(chanPoint)
3✔
273
        return &ChanCloser{
3✔
274
                closeReq:            closeReq,
3✔
275
                state:               closeIdle,
3✔
276
                chanPoint:           chanPoint,
3✔
277
                cid:                 cid,
3✔
278
                cfg:                 cfg,
3✔
279
                negotiationHeight:   negotiationHeight,
3✔
280
                idealFeeRate:        idealFeePerKw,
3✔
281
                localDeliveryScript: deliveryScript,
3✔
282
                priorFeeOffers: make(
3✔
283
                        map[btcutil.Amount]*lnwire.ClosingSigned,
3✔
284
                ),
3✔
285
                locallyInitiated: locallyInitiated,
3✔
286
        }
3✔
287
}
3✔
288

289
// initFeeBaseline computes our ideal fee rate, and also the largest fee we'll
290
// accept given information about the delivery script of the remote party.
291
func (c *ChanCloser) initFeeBaseline() {
3✔
292
        // Depending on if a balance ends up being dust or not, we'll pass a
3✔
293
        // nil TxOut into the EstimateFee call which can handle it.
3✔
294
        var localTxOut, remoteTxOut *wire.TxOut
3✔
295
        if !c.cfg.Channel.LocalBalanceDust() {
6✔
296
                localTxOut = &wire.TxOut{
3✔
297
                        PkScript: c.localDeliveryScript,
3✔
298
                        Value:    0,
3✔
299
                }
3✔
300
        }
3✔
301
        if !c.cfg.Channel.RemoteBalanceDust() {
6✔
302
                remoteTxOut = &wire.TxOut{
3✔
303
                        PkScript: c.remoteDeliveryScript,
3✔
304
                        Value:    0,
3✔
305
                }
3✔
306
        }
3✔
307

308
        // Given the target fee-per-kw, we'll compute what our ideal _total_
309
        // fee will be starting at for this fee negotiation.
310
        c.idealFeeSat = c.cfg.FeeEstimator.EstimateFee(
3✔
311
                0, localTxOut, remoteTxOut, c.idealFeeRate,
3✔
312
        )
3✔
313

3✔
314
        // When we're the initiator, we'll want to also factor in the highest
3✔
315
        // fee we want to pay. This'll either be 3x the ideal fee, or the
3✔
316
        // specified explicit max fee.
3✔
317
        c.maxFee = c.idealFeeSat * defaultMaxFeeMultiplier
3✔
318
        if c.cfg.MaxFee > 0 {
3✔
319
                c.maxFee = c.cfg.FeeEstimator.EstimateFee(
×
320
                        0, localTxOut, remoteTxOut, c.cfg.MaxFee,
×
321
                )
×
322
        }
×
323

324
        chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
3✔
325
                "is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
3✔
326
                int64(c.idealFeeSat), int64(c.maxFee))
3✔
327
}
328

329
// initChanShutdown begins the shutdown process by un-registering the channel,
330
// and creating a valid shutdown message to our target delivery address.
331
func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
3✔
332
        // With both items constructed we'll now send the shutdown message for
3✔
333
        // this particular channel, advertising a shutdown request to our
3✔
334
        // desired closing script.
3✔
335
        shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
3✔
336

3✔
337
        // If this is a taproot channel, then we'll need to also generate a
3✔
338
        // nonce that'll be used sign the co-op close transaction offer.
3✔
339
        if c.cfg.Channel.ChanType().IsTaproot() {
6✔
340
                firstClosingNonce, err := c.cfg.MusigSession.ClosingNonce()
3✔
341
                if err != nil {
3✔
342
                        return nil, err
×
343
                }
×
344

345
                shutdown.ShutdownNonce = lnwire.SomeShutdownNonce(
3✔
346
                        firstClosingNonce.PubNonce,
3✔
347
                )
3✔
348

3✔
349
                chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
3✔
350
                        spew.Sdump(firstClosingNonce.PubNonce))
3✔
351
        }
352

353
        // Before closing, we'll attempt to send a disable update for the
354
        // channel.  We do so before closing the channel as otherwise the
355
        // current edge policy won't be retrievable from the graph.
356
        if err := c.cfg.DisableChannel(c.chanPoint); err != nil {
6✔
357
                chancloserLog.Warnf("Unable to disable channel %v on close: %v",
3✔
358
                        c.chanPoint, err)
3✔
359
        }
3✔
360

361
        chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
3✔
362
                c.chanPoint)
3✔
363

3✔
364
        // At this point, we persist any relevant info regarding the Shutdown
3✔
365
        // message we are about to send in order to ensure that if a
3✔
366
        // re-establish occurs then we will re-send the same Shutdown message.
3✔
367
        shutdownInfo := channeldb.NewShutdownInfo(
3✔
368
                c.localDeliveryScript, c.locallyInitiated,
3✔
369
        )
3✔
370
        err := c.cfg.Channel.MarkShutdownSent(shutdownInfo)
3✔
371
        if err != nil {
3✔
372
                return nil, err
×
373
        }
×
374

375
        return shutdown, nil
3✔
376
}
377

378
// ShutdownChan is the first method that's to be called by the initiator of the
379
// cooperative channel closure. This message returns the shutdown message to
380
// send to the remote party. Upon completion, we enter the
381
// closeShutdownInitiated phase as we await a response.
382
func (c *ChanCloser) ShutdownChan() (*lnwire.Shutdown, error) {
3✔
383
        // If we attempt to shutdown the channel for the first time, and we're not
3✔
384
        // in the closeIdle state, then the caller made an error.
3✔
385
        if c.state != closeIdle {
3✔
386
                return nil, ErrChanAlreadyClosing
×
387
        }
×
388

389
        chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
3✔
390

3✔
391
        shutdownMsg, err := c.initChanShutdown()
3✔
392
        if err != nil {
3✔
393
                return nil, err
×
394
        }
×
395

396
        // With the opening steps complete, we'll transition into the
397
        // closeShutdownInitiated state. In this state, we'll wait until the
398
        // other party sends their version of the shutdown message.
399
        c.state = closeShutdownInitiated
3✔
400

3✔
401
        // Finally, we'll return the shutdown message to the caller so it can
3✔
402
        // send it to the remote peer.
3✔
403
        return shutdownMsg, nil
3✔
404
}
405

406
// ClosingTx returns the fully signed, final closing transaction.
407
//
408
// NOTE: This transaction is only available if the state machine is in the
409
// closeFinished state.
410
func (c *ChanCloser) ClosingTx() (*wire.MsgTx, error) {
3✔
411
        // If the state machine hasn't finished closing the channel, then we'll
3✔
412
        // return an error as we haven't yet computed the closing tx.
3✔
413
        if c.state != closeFinished {
6✔
414
                return nil, ErrChanCloseNotFinished
3✔
415
        }
3✔
416

417
        return c.closingTx, nil
3✔
418
}
419

420
// CloseRequest returns the original close request that prompted the creation
421
// of the state machine.
422
//
423
// NOTE: This will only return a non-nil pointer if we were the initiator of
424
// the cooperative closure workflow.
425
func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
3✔
426
        return c.closeReq
3✔
427
}
3✔
428

429
// Channel returns the channel stored in the config as a
430
// *lnwallet.LightningChannel.
431
//
432
// NOTE: This method will PANIC if the underlying channel implementation isn't
433
// the desired type.
434
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
3✔
435
        // TODO(roasbeef): remove this
3✔
436
        return c.cfg.Channel.(*lnwallet.LightningChannel)
3✔
437
}
3✔
438

439
// NegotiationHeight returns the negotiation height.
440
func (c *ChanCloser) NegotiationHeight() uint32 {
3✔
441
        return c.negotiationHeight
3✔
442
}
3✔
443

444
// validateShutdownScript attempts to match and validate the script provided in
445
// our peer's shutdown message with the upfront shutdown script we have on
446
// record. For any script specified, we also make sure it matches our
447
// requirements. If no upfront shutdown script was set, we do not need to
448
// enforce option upfront shutdown, so the function returns early. If an
449
// upfront script is set, we check whether it matches the script provided by
450
// our peer. If they do not match, we use the disconnect function provided to
451
// disconnect from the peer.
452
func validateShutdownScript(disconnect func() error, upfrontScript,
453
        peerScript lnwire.DeliveryAddress, netParams *chaincfg.Params) error {
3✔
454

3✔
455
        // Either way, we'll make sure that the script passed meets our
3✔
456
        // standards. The upfrontScript should have already been checked at an
3✔
457
        // earlier stage, but we'll repeat the check here for defense in depth.
3✔
458
        if len(upfrontScript) != 0 {
6✔
459
                if !lnwallet.ValidateUpfrontShutdown(upfrontScript, netParams) {
3✔
460
                        return ErrInvalidShutdownScript
×
461
                }
×
462
        }
463
        if len(peerScript) != 0 {
6✔
464
                if !lnwallet.ValidateUpfrontShutdown(peerScript, netParams) {
3✔
465
                        return ErrInvalidShutdownScript
×
466
                }
×
467
        }
468

469
        // If no upfront shutdown script was set, return early because we do
470
        // not need to enforce closure to a specific script.
471
        if len(upfrontScript) == 0 {
6✔
472
                return nil
3✔
473
        }
3✔
474

475
        // If an upfront shutdown script was provided, disconnect from the peer, as
476
        // per BOLT 2, and return an error.
477
        if !bytes.Equal(upfrontScript, peerScript) {
3✔
478
                chancloserLog.Warnf("peer's script: %x does not match upfront "+
×
479
                        "shutdown script: %x", peerScript, upfrontScript)
×
480

×
481
                // Disconnect from the peer because they have violated option upfront
×
482
                // shutdown.
×
483
                if err := disconnect(); err != nil {
×
484
                        return err
×
485
                }
×
486

487
                return ErrUpfrontShutdownScriptMismatch
×
488
        }
489

490
        return nil
3✔
491
}
492

493
// ReceiveShutdown takes a raw Shutdown message and uses it to try and advance
494
// the ChanCloser state machine, failing if it is coming in at an invalid time.
495
// If appropriate, it will also generate a Shutdown message of its own to send
496
// out to the peer. It is possible for this method to return None when no error
497
// occurred.
498
func (c *ChanCloser) ReceiveShutdown(msg lnwire.Shutdown) (
499
        fn.Option[lnwire.Shutdown], error) {
3✔
500

3✔
501
        noShutdown := fn.None[lnwire.Shutdown]()
3✔
502

3✔
503
        switch c.state {
3✔
504
        // If we're in the close idle state, and we're receiving a channel
505
        // closure related message, then this indicates that we're on the
506
        // receiving side of an initiated channel closure.
507
        case closeIdle:
3✔
508
                // As we're the responder to this shutdown (the other party
3✔
509
                // wants to close), we'll check if this is a frozen channel or
3✔
510
                // not. If the channel is frozen and we were not also the
3✔
511
                // initiator of the channel opening, then we'll deny their close
3✔
512
                // attempt.
3✔
513
                chanInitiator := c.cfg.Channel.IsInitiator()
3✔
514
                if !chanInitiator {
6✔
515
                        absoluteThawHeight, err :=
3✔
516
                                c.cfg.Channel.AbsoluteThawHeight()
3✔
517
                        if err != nil {
3✔
518
                                return noShutdown, err
×
519
                        }
×
520
                        if c.negotiationHeight < absoluteThawHeight {
3✔
521
                                return noShutdown, fmt.Errorf("initiator "+
×
522
                                        "attempting to co-op close frozen "+
×
523
                                        "ChannelPoint(%v) (current_height=%v, "+
×
524
                                        "thaw_height=%v)", c.chanPoint,
×
525
                                        c.negotiationHeight, absoluteThawHeight)
×
526
                        }
×
527
                }
528

529
                // If the remote node opened the channel with option upfront
530
                // shutdown script, check that the script they provided matches.
531
                if err := validateShutdownScript(
3✔
532
                        c.cfg.Disconnect,
3✔
533
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
3✔
534
                        msg.Address, c.cfg.ChainParams,
3✔
535
                ); err != nil {
3✔
536
                        return noShutdown, err
×
537
                }
×
538

539
                // Once we have checked that the other party has not violated
540
                // option upfront shutdown we set their preference for delivery
541
                // address. We'll use this when we craft the closure
542
                // transaction.
543
                c.remoteDeliveryScript = msg.Address
3✔
544

3✔
545
                // We'll generate a shutdown message of our own to send across
3✔
546
                // the wire.
3✔
547
                localShutdown, err := c.initChanShutdown()
3✔
548
                if err != nil {
3✔
549
                        return noShutdown, err
×
550
                }
×
551

552
                // If this is a taproot channel, then we'll want to stash the
553
                // remote nonces so we can properly create a new musig
554
                // session for signing.
555
                if c.cfg.Channel.ChanType().IsTaproot() {
6✔
556
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
3✔
557
                                errNoShutdownNonce,
3✔
558
                        )
3✔
559
                        if err != nil {
3✔
560
                                return noShutdown, err
×
561
                        }
×
562

563
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
3✔
564
                                PubNonce: shutdownNonce,
3✔
565
                        })
3✔
566
                }
567

568
                chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
3✔
569
                        c.chanPoint)
3✔
570

3✔
571
                // After the other party receives this message, we'll actually
3✔
572
                // start the final stage of the closure process: fee
3✔
573
                // negotiation. So we'll update our internal state to reflect
3✔
574
                // this, so we can handle the next message sent.
3✔
575
                c.state = closeAwaitingFlush
3✔
576

3✔
577
                return fn.Some(*localShutdown), err
3✔
578

579
        case closeShutdownInitiated:
3✔
580
                // If the remote node opened the channel with option upfront
3✔
581
                // shutdown script, check that the script they provided matches.
3✔
582
                if err := validateShutdownScript(
3✔
583
                        c.cfg.Disconnect,
3✔
584
                        c.cfg.Channel.RemoteUpfrontShutdownScript(),
3✔
585
                        msg.Address, c.cfg.ChainParams,
3✔
586
                ); err != nil {
3✔
587
                        return noShutdown, err
×
588
                }
×
589

590
                // Now that we know this is a valid shutdown message and
591
                // address, we'll record their preferred delivery closing
592
                // script.
593
                c.remoteDeliveryScript = msg.Address
3✔
594

3✔
595
                // At this point, we can now start the fee negotiation state, by
3✔
596
                // constructing and sending our initial signature for what we
3✔
597
                // think the closing transaction should look like.
3✔
598
                c.state = closeAwaitingFlush
3✔
599

3✔
600
                // If this is a taproot channel, then we'll want to stash the
3✔
601
                // local+remote nonces so we can properly create a new musig
3✔
602
                // session for signing.
3✔
603
                if c.cfg.Channel.ChanType().IsTaproot() {
6✔
604
                        shutdownNonce, err := msg.ShutdownNonce.UnwrapOrErrV(
3✔
605
                                errNoShutdownNonce,
3✔
606
                        )
3✔
607
                        if err != nil {
3✔
608
                                return noShutdown, err
×
609
                        }
×
610

611
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
3✔
612
                                PubNonce: shutdownNonce,
3✔
613
                        })
3✔
614
                }
615

616
                chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
3✔
617
                        "received, entering fee negotiation", c.chanPoint)
3✔
618

3✔
619
                return noShutdown, nil
3✔
620

621
        default:
×
622
                // Otherwise we are not in a state where we can accept this
×
623
                // message.
×
624
                return noShutdown, ErrInvalidState
×
625
        }
626
}
627

628
// BeginNegotiation should be called when we have definitively reached a clean
629
// channel state and are ready to cooperatively arrive at a closing transaction.
630
// If it is our responsibility to kick off the negotiation, this method will
631
// generate a ClosingSigned message. If it is the remote's responsibility, then
632
// it will not. In either case it will transition the ChanCloser state machine
633
// to the negotiation phase wherein ClosingSigned messages are exchanged until
634
// a mutually agreeable result is achieved.
635
func (c *ChanCloser) BeginNegotiation() (fn.Option[lnwire.ClosingSigned],
636
        error) {
3✔
637

3✔
638
        noClosingSigned := fn.None[lnwire.ClosingSigned]()
3✔
639

3✔
640
        switch c.state {
3✔
641
        case closeAwaitingFlush:
3✔
642
                // Now that we know their desired delivery script, we can
3✔
643
                // compute what our max/ideal fee will be.
3✔
644
                c.initFeeBaseline()
3✔
645

3✔
646
                // Before continuing, mark the channel as cooperatively closed
3✔
647
                // with a nil txn. Even though we haven't negotiated the final
3✔
648
                // txn, this guarantees that our listchannels rpc will be
3✔
649
                // externally consistent, and reflect that the channel is being
3✔
650
                // shutdown by the time the closing request returns.
3✔
651
                err := c.cfg.Channel.MarkCoopBroadcasted(
3✔
652
                        nil, c.locallyInitiated,
3✔
653
                )
3✔
654
                if err != nil {
3✔
655
                        return noClosingSigned, err
×
656
                }
×
657

658
                // At this point, we can now start the fee negotiation state, by
659
                // constructing and sending our initial signature for what we
660
                // think the closing transaction should look like.
661
                c.state = closeFeeNegotiation
3✔
662

3✔
663
                if !c.cfg.Channel.IsInitiator() {
6✔
664
                        // By default this means we do nothing, but we do want
3✔
665
                        // to check if we have a cached remote offer to process.
3✔
666
                        // If we do, we'll process it here.
3✔
667
                        res := noClosingSigned
3✔
668
                        err = nil
3✔
669
                        c.cachedClosingSigned.WhenSome(
3✔
670
                                func(cs lnwire.ClosingSigned) {
6✔
671
                                        res, err = c.ReceiveClosingSigned(cs)
3✔
672
                                },
3✔
673
                        )
674

675
                        return res, err
3✔
676
                }
677

678
                // We'll craft our initial close proposal in order to keep the
679
                // negotiation moving, but only if we're the initiator.
680
                closingSigned, err := c.proposeCloseSigned(c.idealFeeSat)
3✔
681
                if err != nil {
3✔
682
                        return noClosingSigned,
×
683
                                fmt.Errorf("unable to sign new co op "+
×
684
                                        "close offer: %w", err)
×
685
                }
×
686

687
                return fn.Some(*closingSigned), nil
3✔
688

689
        default:
×
690
                return noClosingSigned, ErrInvalidState
×
691
        }
692
}
693

694
// ReceiveClosingSigned is a method that should be called whenever we receive a
695
// ClosingSigned message from the wire. It may or may not return a
696
// ClosingSigned of our own to send back to the remote.
697
func (c *ChanCloser) ReceiveClosingSigned( //nolint:funlen
698
        msg lnwire.ClosingSigned) (fn.Option[lnwire.ClosingSigned], error) {
3✔
699

3✔
700
        noClosing := fn.None[lnwire.ClosingSigned]()
3✔
701

3✔
702
        switch c.state {
3✔
703
        case closeAwaitingFlush:
3✔
704
                // If we hit this case it either means there's a protocol
3✔
705
                // violation or that our chanCloser received the remote offer
3✔
706
                // before the link finished processing the channel flush.
3✔
707
                c.cachedClosingSigned = fn.Some(msg)
3✔
708
                return fn.None[lnwire.ClosingSigned](), nil
3✔
709

710
        case closeFeeNegotiation:
3✔
711
                // If this is a taproot channel, then it MUST have a partial
3✔
712
                // signature set at this point.
3✔
713
                isTaproot := c.cfg.Channel.ChanType().IsTaproot()
3✔
714
                if isTaproot && msg.PartialSig.IsNone() {
3✔
715
                        return noClosing,
×
716
                                fmt.Errorf("partial sig not set " +
×
717
                                        "for taproot chan")
×
718
                }
×
719

720
                isInitiator := c.cfg.Channel.IsInitiator()
3✔
721

3✔
722
                // We'll compare the proposed total fee, to what we've proposed
3✔
723
                // during the negotiations. If it doesn't match any of our
3✔
724
                // prior offers, then we'll attempt to ratchet the fee closer
3✔
725
                // to our ideal fee.
3✔
726
                remoteProposedFee := msg.FeeSatoshis
3✔
727

3✔
728
                _, feeMatchesOffer := c.priorFeeOffers[remoteProposedFee]
3✔
729
                switch {
3✔
730
                // For taproot channels, since nonces are involved, we can't do
731
                // the existing co-op close negotiation process without going
732
                // to a fully round based model. Rather than do this, we'll
733
                // just accept the very first offer by the initiator.
734
                case isTaproot && !isInitiator:
3✔
735
                        chancloserLog.Infof("ChannelPoint(%v) accepting "+
3✔
736
                                "initiator fee of %v", c.chanPoint,
3✔
737
                                remoteProposedFee)
3✔
738

3✔
739
                        // To auto-accept the initiators proposal, we'll just
3✔
740
                        // send back a signature w/ the same offer. We don't
3✔
741
                        // send the message here, as we can drop down and
3✔
742
                        // finalize the closure and broadcast, then echo back
3✔
743
                        // to Alice the final signature.
3✔
744
                        _, err := c.proposeCloseSigned(remoteProposedFee)
3✔
745
                        if err != nil {
3✔
746
                                return noClosing, fmt.Errorf("unable to sign "+
×
747
                                        "new co op close offer: %w", err)
×
748
                        }
×
749

750
                // Otherwise, if we are the initiator, and we just sent a
751
                // signature for a taproot channel, then we'll ensure that the
752
                // fee rate matches up exactly.
753
                case isTaproot && isInitiator && !feeMatchesOffer:
×
754
                        return noClosing,
×
755
                                fmt.Errorf("fee rate for "+
×
756
                                        "taproot channels was not accepted: "+
×
757
                                        "sent %v, got %v",
×
758
                                        c.idealFeeSat, remoteProposedFee)
×
759

760
                // If we're the initiator of the taproot channel, and we had
761
                // our fee echo'd back, then it's all good, and we can proceed
762
                // with final broadcast.
763
                case isTaproot && isInitiator && feeMatchesOffer:
3✔
764
                        break
3✔
765

766
                // Otherwise, if this is a normal segwit v0 channel, and the
767
                // fee doesn't match our offer, then we'll try to "negotiate" a
768
                // new fee.
769
                case !feeMatchesOffer:
3✔
770
                        // We'll now attempt to ratchet towards a fee deemed
3✔
771
                        // acceptable by both parties, factoring in our ideal
3✔
772
                        // fee rate, and the last proposed fee by both sides.
3✔
773
                        proposal := calcCompromiseFee(
3✔
774
                                c.chanPoint, c.idealFeeSat, c.lastFeeProposal,
3✔
775
                                remoteProposedFee,
3✔
776
                        )
3✔
777
                        if c.cfg.Channel.IsInitiator() && proposal > c.maxFee {
3✔
778
                                return noClosing, fmt.Errorf(
×
779
                                        "%w: %v > %v",
×
780
                                        ErrProposalExceedsMaxFee,
×
781
                                        proposal, c.maxFee)
×
782
                        }
×
783

784
                        // With our new fee proposal calculated, we'll craft a
785
                        // new close signed signature to send to the other
786
                        // party so we can continue the fee negotiation
787
                        // process.
788
                        closeSigned, err := c.proposeCloseSigned(proposal)
3✔
789
                        if err != nil {
3✔
790
                                return noClosing, fmt.Errorf("unable to sign "+
×
791
                                        "new co op close offer: %w", err)
×
792
                        }
×
793

794
                        // If the compromise fee doesn't match what the peer
795
                        // proposed, then we'll return this latest close signed
796
                        // message so we can continue negotiation.
797
                        if proposal != remoteProposedFee {
3✔
798
                                chancloserLog.Debugf("ChannelPoint(%v): close "+
×
799
                                        "tx fee disagreement, continuing "+
×
800
                                        "negotiation", c.chanPoint)
×
801

×
802
                                return fn.Some(*closeSigned), nil
×
803
                        }
×
804
                }
805

806
                chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, "+
3✔
807
                        "ending negotiation", c.chanPoint, remoteProposedFee)
3✔
808

3✔
809
                // Otherwise, we've agreed on a fee for the closing
3✔
810
                // transaction! We'll craft the final closing transaction so we
3✔
811
                // can broadcast it to the network.
3✔
812
                var (
3✔
813
                        localSig, remoteSig input.Signature
3✔
814
                        closeOpts           []lnwallet.ChanCloseOpt
3✔
815
                        err                 error
3✔
816
                )
3✔
817
                matchingSig := c.priorFeeOffers[remoteProposedFee]
3✔
818
                if c.cfg.Channel.ChanType().IsTaproot() {
6✔
819
                        localWireSig, err := matchingSig.PartialSig.UnwrapOrErrV( //nolint:lll
3✔
820
                                fmt.Errorf("none local sig"),
3✔
821
                        )
3✔
822
                        if err != nil {
3✔
823
                                return noClosing, err
×
824
                        }
×
825
                        remoteWireSig, err := msg.PartialSig.UnwrapOrErrV(
3✔
826
                                fmt.Errorf("none remote sig"),
3✔
827
                        )
3✔
828
                        if err != nil {
3✔
829
                                return noClosing, err
×
830
                        }
×
831

832
                        muSession := c.cfg.MusigSession
3✔
833
                        localSig, remoteSig, closeOpts, err = muSession.CombineClosingOpts( //nolint:lll
3✔
834
                                localWireSig, remoteWireSig,
3✔
835
                        )
3✔
836
                        if err != nil {
3✔
837
                                return noClosing, err
×
838
                        }
×
839
                } else {
3✔
840
                        localSig, err = matchingSig.Signature.ToSignature()
3✔
841
                        if err != nil {
3✔
842
                                return noClosing, err
×
843
                        }
×
844
                        remoteSig, err = msg.Signature.ToSignature()
3✔
845
                        if err != nil {
3✔
846
                                return noClosing, err
×
847
                        }
×
848
                }
849

850
                closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
3✔
851
                        localSig, remoteSig, c.localDeliveryScript,
3✔
852
                        c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
3✔
853
                )
3✔
854
                if err != nil {
3✔
855
                        return noClosing, err
×
856
                }
×
857
                c.closingTx = closeTx
3✔
858

3✔
859
                // Before publishing the closing tx, we persist it to the
3✔
860
                // database, such that it can be republished if something goes
3✔
861
                // wrong.
3✔
862
                err = c.cfg.Channel.MarkCoopBroadcasted(
3✔
863
                        closeTx, c.locallyInitiated,
3✔
864
                )
3✔
865
                if err != nil {
3✔
866
                        return noClosing, err
×
867
                }
×
868

869
                // With the closing transaction crafted, we'll now broadcast it
870
                // to the network.
871
                chancloserLog.Infof("Broadcasting cooperative close tx: %v",
3✔
872
                        newLogClosure(func() string {
6✔
873
                                return spew.Sdump(closeTx)
3✔
874
                        }),
3✔
875
                )
876

877
                // Create a close channel label.
878
                chanID := c.cfg.Channel.ShortChanID()
3✔
879
                closeLabel := labels.MakeLabel(
3✔
880
                        labels.LabelTypeChannelClose, &chanID,
3✔
881
                )
3✔
882

3✔
883
                if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
3✔
884
                        return noClosing, err
×
885
                }
×
886

887
                // Finally, we'll transition to the closeFinished state, and
888
                // also return the final close signed message we sent.
889
                // Additionally, we return true for the second argument to
890
                // indicate we're finished with the channel closing
891
                // negotiation.
892
                c.state = closeFinished
3✔
893
                matchingOffer := c.priorFeeOffers[remoteProposedFee]
3✔
894

3✔
895
                return fn.Some(*matchingOffer), nil
3✔
896

897
        // If we received a message while in the closeFinished state, then this
898
        // should only be the remote party echoing the last ClosingSigned
899
        // message that we agreed on.
900
        case closeFinished:
3✔
901

3✔
902
                // There's no more to do as both sides should have already
3✔
903
                // broadcast the closing transaction at this state.
3✔
904
                return noClosing, nil
3✔
905

906
        default:
×
907
                return noClosing, ErrInvalidState
×
908
        }
909
}
910

911
// proposeCloseSigned attempts to propose a new signature for the closing
912
// transaction for a channel based on the prior fee negotiations and our current
913
// compromise fee.
914
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (
915
        *lnwire.ClosingSigned, error) {
3✔
916

3✔
917
        var (
3✔
918
                closeOpts []lnwallet.ChanCloseOpt
3✔
919
                err       error
3✔
920
        )
3✔
921

3✔
922
        // If this is a taproot channel, then we'll include the musig session
3✔
923
        // generated for the next co-op close negotiation round.
3✔
924
        if c.cfg.Channel.ChanType().IsTaproot() {
6✔
925
                closeOpts, err = c.cfg.MusigSession.ProposalClosingOpts()
3✔
926
                if err != nil {
3✔
927
                        return nil, err
×
928
                }
×
929
        }
930

931
        rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
3✔
932
                fee, c.localDeliveryScript, c.remoteDeliveryScript,
3✔
933
                closeOpts...,
3✔
934
        )
3✔
935
        if err != nil {
3✔
936
                return nil, err
×
937
        }
×
938

939
        // We'll note our last signature and proposed fee so when the remote
940
        // party responds we'll be able to decide if we've agreed on fees or
941
        // not.
942
        var (
3✔
943
                parsedSig  lnwire.Sig
3✔
944
                partialSig *lnwire.PartialSigWithNonce
3✔
945
        )
3✔
946
        if c.cfg.Channel.ChanType().IsTaproot() {
6✔
947
                musig, ok := rawSig.(*lnwallet.MusigPartialSig)
3✔
948
                if !ok {
3✔
949
                        return nil, fmt.Errorf("expected MusigPartialSig, "+
×
950
                                "got %T", rawSig)
×
951
                }
×
952

953
                partialSig = musig.ToWireSig()
3✔
954
        } else {
3✔
955
                parsedSig, err = lnwire.NewSigFromSignature(rawSig)
3✔
956
                if err != nil {
3✔
957
                        return nil, err
×
958
                }
×
959
        }
960

961
        c.lastFeeProposal = fee
3✔
962

3✔
963
        chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+
3✔
964
                "close chan", c.chanPoint, int64(fee))
3✔
965

3✔
966
        // We'll assemble a ClosingSigned message using this information and
3✔
967
        // return it to the caller so we can kick off the final stage of the
3✔
968
        // channel closure process.
3✔
969
        closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig)
3✔
970

3✔
971
        // For musig2 channels, the main sig is blank, and instead we'll send
3✔
972
        // over a partial signature which'll be combined once our offer is
3✔
973
        // accepted.
3✔
974
        if partialSig != nil {
6✔
975
                closeSignedMsg.PartialSig = lnwire.SomePartialSig(
3✔
976
                        partialSig.PartialSig,
3✔
977
                )
3✔
978
        }
3✔
979

980
        // We'll also save this close signed, in the case that the remote party
981
        // accepts our offer. This way, we don't have to re-sign.
982
        c.priorFeeOffers[fee] = closeSignedMsg
3✔
983

3✔
984
        return closeSignedMsg, nil
3✔
985
}
986

987
// feeInAcceptableRange returns true if the passed remote fee is deemed to be
988
// in an "acceptable" range to our local fee. This is an attempt at a
989
// compromise and to ensure that the fee negotiation has a stopping point. We
990
// consider their fee acceptable if it's within 30% of our fee.
991
func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool {
×
992
        // If our offer is lower than theirs, then we'll accept their offer if
×
993
        // it's no more than 30% *greater* than our current offer.
×
994
        if localFee < remoteFee {
×
995
                acceptableRange := localFee + ((localFee * 3) / 10)
×
996
                return remoteFee <= acceptableRange
×
997
        }
×
998

999
        // If our offer is greater than theirs, then we'll accept their offer if
1000
        // it's no more than 30% *less* than our current offer.
1001
        acceptableRange := localFee - ((localFee * 3) / 10)
×
1002
        return remoteFee >= acceptableRange
×
1003
}
1004

1005
// ratchetFee is our step function used to inch our fee closer to something
1006
// that both sides can agree on. If up is true, then we'll attempt to increase
1007
// our offered fee. Otherwise, if up is false, then we'll attempt to decrease
1008
// our offered fee.
1009
func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount {
×
1010
        // If we need to ratchet up, then we'll increase our fee by 10%.
×
1011
        if up {
×
1012
                return fee + ((fee * 1) / 10)
×
1013
        }
×
1014

1015
        // Otherwise, we'll *decrease* our fee by 10%.
1016
        return fee - ((fee * 1) / 10)
×
1017
}
1018

1019
// calcCompromiseFee performs the current fee negotiation algorithm, taking
1020
// into consideration our ideal fee based on current fee environment, the fee
1021
// we last proposed (if any), and the fee proposed by the peer.
1022
func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
1023
        remoteFee btcutil.Amount) btcutil.Amount {
3✔
1024

3✔
1025
        // TODO(roasbeef): take in number of rounds as well?
3✔
1026

3✔
1027
        chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, "+
3✔
1028
                "ideal=%v, last_sent=%v, remote_offer=%v", chanPoint,
3✔
1029
                int64(ourIdealFee), int64(lastSentFee), int64(remoteFee))
3✔
1030

3✔
1031
        // Otherwise, we'll need to attempt to make a fee compromise if this is
3✔
1032
        // the second round, and neither side has agreed on fees.
3✔
1033
        switch {
3✔
1034
        // If their proposed fee is identical to our ideal fee, then we'll go
1035
        // with that as we can short circuit the fee negotiation. Similarly, if
1036
        // we haven't sent an offer yet, we'll default to our ideal fee.
1037
        case ourIdealFee == remoteFee || lastSentFee == 0:
3✔
1038
                return ourIdealFee
3✔
1039

1040
        // If the last fee we sent, is equal to the fee the remote party is
1041
        // offering, then we can simply return this fee as the negotiation is
1042
        // over.
1043
        case remoteFee == lastSentFee:
×
1044
                return lastSentFee
×
1045

1046
        // If the fee the remote party is offering is less than the last one we
1047
        // sent, then we'll need to ratchet down in order to move our offer
1048
        // closer to theirs.
1049
        case remoteFee < lastSentFee:
×
1050
                // If the fee is lower, but still acceptable, then we'll just
×
1051
                // return this fee and end the negotiation.
×
1052
                if feeInAcceptableRange(lastSentFee, remoteFee) {
×
1053
                        chancloserLog.Infof("ChannelPoint(%v): proposed "+
×
1054
                                "remote fee is close enough, capitulating",
×
1055
                                chanPoint)
×
1056

×
1057
                        return remoteFee
×
1058
                }
×
1059

1060
                // Otherwise, we'll ratchet the fee *down* using our current
1061
                // algorithm.
1062
                return ratchetFee(lastSentFee, false)
×
1063

1064
        // If the fee the remote party is offering is greater than the last one
1065
        // we sent, then we'll ratchet up in order to ensure we terminate
1066
        // eventually.
1067
        case remoteFee > lastSentFee:
×
1068
                // If the fee is greater, but still acceptable, then we'll just
×
1069
                // return this fee in order to put an end to the negotiation.
×
1070
                if feeInAcceptableRange(lastSentFee, remoteFee) {
×
1071
                        chancloserLog.Infof("ChannelPoint(%v): proposed "+
×
1072
                                "remote fee is close enough, capitulating",
×
1073
                                chanPoint)
×
1074

×
1075
                        return remoteFee
×
1076
                }
×
1077

1078
                // Otherwise, we'll ratchet the fee up using our current
1079
                // algorithm.
1080
                return ratchetFee(lastSentFee, true)
×
1081

1082
        default:
×
1083
                // TODO(roasbeef): fail if their fee isn't in expected range
×
1084
                return remoteFee
×
1085
        }
1086
}
1087

1088
// ParseUpfrontShutdownAddress attempts to parse an upfront shutdown address.
1089
// If the address is empty, it returns nil. If it successfully decoded the
1090
// address, it returns a script that pays out to the address.
1091
func ParseUpfrontShutdownAddress(address string,
1092
        params *chaincfg.Params) (lnwire.DeliveryAddress, error) {
3✔
1093

3✔
1094
        if len(address) == 0 {
6✔
1095
                return nil, nil
3✔
1096
        }
3✔
1097

1098
        addr, err := btcutil.DecodeAddress(
3✔
1099
                address, params,
3✔
1100
        )
3✔
1101
        if err != nil {
3✔
1102
                return nil, fmt.Errorf("invalid address: %w", err)
×
1103
        }
×
1104

1105
        if !addr.IsForNet(params) {
3✔
1106
                return nil, fmt.Errorf("invalid address: %v is not a %s "+
×
1107
                        "address", addr, params.Name)
×
1108
        }
×
1109

1110
        return txscript.PayToAddrScript(addr)
3✔
1111
}
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