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

lightningnetwork / lnd / 10203737448

01 Aug 2024 06:26PM UTC coverage: 58.674% (+0.05%) from 58.627%
10203737448

push

github

web-flow
Merge pull request #8938 from bhandras/etcd-leader-election-fixups

multi: check leader status with our health checker to correctly shut down LND if network partitions

28 of 73 new or added lines in 6 files covered. (38.36%)

117 existing lines in 18 files now uncovered.

125392 of 213710 relevant lines covered (58.67%)

28078.2 hits per line

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

83.11
/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/lntypes"
19
        "github.com/lightningnetwork/lnd/lnutils"
20
        "github.com/lightningnetwork/lnd/lnwallet"
21
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
22
        "github.com/lightningnetwork/lnd/lnwire"
23
)
24

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

153
        // cfg holds the configuration for this ChanCloser instance.
154
        cfg ChanCloseCfg
155

156
        // chanPoint is the full channel point of the target channel.
157
        chanPoint wire.OutPoint
158

159
        // cid is the full channel ID of the target channel.
160
        cid lnwire.ChannelID
161

162
        // negotiationHeight is the height that the fee negotiation begun at.
163
        negotiationHeight uint32
164

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

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

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

179
        // idealFeeRate is our ideal fee rate.
180
        idealFeeRate chainfee.SatPerKWeight
181

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

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

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

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

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

211
        // closer is ChannelParty who initiated the coop close
212
        closer lntypes.ChannelParty
213

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

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

20✔
226
        var weightEstimator input.TxWeightEstimator
20✔
227

20✔
228
        if chanType.IsTaproot() {
20✔
229
                weightEstimator.AddWitnessInput(
×
230
                        input.TaprootSignatureWitnessSize,
×
231
                )
×
232
        } else {
20✔
233
                weightEstimator.AddWitnessInput(input.MultiSigWitnessSize)
20✔
234
        }
20✔
235

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

245
        totalWeight := weightEstimator.Weight()
20✔
246

20✔
247
        return idealFeeRate.FeeForWeight(totalWeight)
20✔
248
}
249

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

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

20✔
263
        return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
20✔
264
}
20✔
265

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

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

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

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

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

327
        chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
15✔
328
                "is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
15✔
329
                int64(c.idealFeeSat), int64(c.maxFee))
15✔
330
}
331

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

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

348
                shutdown.ShutdownNonce = lnwire.SomeShutdownNonce(
6✔
349
                        firstClosingNonce.PubNonce,
6✔
350
                )
6✔
351

6✔
352
                chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
6✔
353
                        spew.Sdump(firstClosingNonce.PubNonce))
6✔
354
        }
355

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

364
        chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
15✔
365
                c.chanPoint)
15✔
366

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

378
        return shutdown, nil
15✔
379
}
380

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

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

11✔
394
        shutdownMsg, err := c.initChanShutdown()
11✔
395
        if err != nil {
11✔
396
                return nil, err
×
397
        }
×
398

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

11✔
404
        // Finally, we'll return the shutdown message to the caller so it can
11✔
405
        // send it to the remote peer.
11✔
406
        return shutdownMsg, nil
11✔
407
}
408

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

420
        return c.closingTx, nil
15✔
421
}
422

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

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

442
// NegotiationHeight returns the negotiation height.
443
func (c *ChanCloser) NegotiationHeight() uint32 {
8✔
444
        return c.negotiationHeight
8✔
445
}
8✔
446

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

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

472
        // If no upfront shutdown script was set, return early because we do
473
        // not need to enforce closure to a specific script.
474
        if len(upfrontScript) == 0 {
64✔
475
                return nil
31✔
476
        }
31✔
477

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

1✔
484
                // Disconnect from the peer because they have violated option upfront
1✔
485
                // shutdown.
1✔
486
                if err := disconnect(); err != nil {
1✔
487
                        return err
×
488
                }
×
489

490
                return ErrUpfrontShutdownScriptMismatch
1✔
491
        }
492

493
        return nil
5✔
494
}
495

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

11✔
504
        noShutdown := fn.None[lnwire.Shutdown]()
11✔
505

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

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

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

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

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

566
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
5✔
567
                                PubNonce: shutdownNonce,
5✔
568
                        })
5✔
569
                }
570

571
                chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
8✔
572
                        c.chanPoint)
8✔
573

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

8✔
580
                return fn.Some(*localShutdown), err
8✔
581

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

593
                // Now that we know this is a valid shutdown message and
594
                // address, we'll record their preferred delivery closing
595
                // script.
596
                c.remoteDeliveryScript = msg.Address
7✔
597

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

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

614
                        c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
5✔
615
                                PubNonce: shutdownNonce,
5✔
616
                        })
5✔
617
                }
618

619
                chancloserLog.Infof("ChannelPoint(%v): shutdown response "+
7✔
620
                        "received, entering fee negotiation", c.chanPoint)
7✔
621

7✔
622
                return noShutdown, nil
7✔
623

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

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

11✔
641
        noClosingSigned := fn.None[lnwire.ClosingSigned]()
11✔
642

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

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

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

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

678
                        return res, err
5✔
679
                }
680

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

690
                return fn.Some(*closingSigned), nil
10✔
691

692
        default:
×
693
                return noClosingSigned, ErrInvalidState
×
694
        }
695
}
696

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

18✔
703
        noClosing := fn.None[lnwire.ClosingSigned]()
18✔
704

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

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

723
                isInitiator := c.cfg.Channel.IsInitiator()
17✔
724

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

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

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

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

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

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

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

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

4✔
805
                                return fn.Some(*closeSigned), nil
4✔
806
                        }
4✔
807
                }
808

809
                chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, "+
10✔
810
                        "ending negotiation", c.chanPoint, remoteProposedFee)
10✔
811

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

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

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

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

872
                // With the closing transaction crafted, we'll now broadcast it
873
                // to the network.
874
                chancloserLog.Infof("Broadcasting cooperative close tx: %v",
10✔
875
                        lnutils.SpewLogClosure(closeTx))
10✔
876

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

10✔
883
                if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
10✔
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
10✔
893
                matchingOffer := c.priorFeeOffers[remoteProposedFee]
10✔
894

10✔
895
                return fn.Some(*matchingOffer), nil
10✔
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:
5✔
901

5✔
902
                // There's no more to do as both sides should have already
5✔
903
                // broadcast the closing transaction at this state.
5✔
904
                return noClosing, nil
5✔
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) {
16✔
916

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

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

931
        rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
16✔
932
                fee, c.localDeliveryScript, c.remoteDeliveryScript,
16✔
933
                closeOpts...,
16✔
934
        )
16✔
935
        if err != nil {
16✔
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 (
16✔
943
                parsedSig  lnwire.Sig
16✔
944
                partialSig *lnwire.PartialSigWithNonce
16✔
945
        )
16✔
946
        if c.cfg.Channel.ChanType().IsTaproot() {
22✔
947
                musig, ok := rawSig.(*lnwallet.MusigPartialSig)
6✔
948
                if !ok {
6✔
949
                        return nil, fmt.Errorf("expected MusigPartialSig, "+
×
950
                                "got %T", rawSig)
×
951
                }
×
952

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

961
        c.lastFeeProposal = fee
15✔
962

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

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

15✔
971
        // For musig2 channels, the main sig is blank, and instead we'll send
15✔
972
        // over a partial signature which'll be combined once our offer is
15✔
973
        // accepted.
15✔
974
        if partialSig != nil {
21✔
975
                closeSignedMsg.PartialSig = lnwire.SomePartialSig(
6✔
976
                        partialSig.PartialSig,
6✔
977
                )
6✔
978
        }
6✔
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
15✔
983

15✔
984
        return closeSignedMsg, nil
15✔
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 {
6✔
992
        // If our offer is lower than theirs, then we'll accept their offer if
6✔
993
        // it's no more than 30% *greater* than our current offer.
6✔
994
        if localFee < remoteFee {
12✔
995
                acceptableRange := localFee + ((localFee * 3) / 10)
6✔
996
                return remoteFee <= acceptableRange
6✔
997
        }
6✔
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 {
6✔
1010
        // If we need to ratchet up, then we'll increase our fee by 10%.
6✔
1011
        if up {
12✔
1012
                return fee + ((fee * 1) / 10)
6✔
1013
        }
6✔
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 {
10✔
1024

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

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

10✔
1031
        // Otherwise, we'll need to attempt to make a fee compromise if this is
10✔
1032
        // the second round, and neither side has agreed on fees.
10✔
1033
        switch {
10✔
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:
4✔
1038
                return ourIdealFee
4✔
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:
6✔
1068
                // If the fee is greater, but still acceptable, then we'll just
6✔
1069
                // return this fee in order to put an end to the negotiation.
6✔
1070
                if feeInAcceptableRange(lastSentFee, remoteFee) {
6✔
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)
6✔
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) {
19✔
1093

19✔
1094
        if len(address) == 0 {
30✔
1095
                return nil, nil
11✔
1096
        }
11✔
1097

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

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

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