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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 hits per line

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

32.99
/lnwallet/chancloser/rbf_coop_states.go
1
package chancloser
2

3
import (
4
        "fmt"
5

6
        "github.com/btcsuite/btcd/btcec/v2"
7
        "github.com/btcsuite/btcd/btcutil"
8
        "github.com/btcsuite/btcd/chaincfg"
9
        "github.com/btcsuite/btcd/wire"
10
        "github.com/lightningnetwork/lnd/chainntnfs"
11
        "github.com/lightningnetwork/lnd/channeldb"
12
        "github.com/lightningnetwork/lnd/fn/v2"
13
        "github.com/lightningnetwork/lnd/input"
14
        "github.com/lightningnetwork/lnd/lntypes"
15
        "github.com/lightningnetwork/lnd/lnwallet"
16
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
17
        "github.com/lightningnetwork/lnd/lnwire"
18
        "github.com/lightningnetwork/lnd/protofsm"
19
)
20

21
var (
22
        // ErrInvalidStateTransition is returned when we receive an unexpected
23
        // event for a given state.
24
        ErrInvalidStateTransition = fmt.Errorf("invalid state transition")
25

26
        // ErrTooManySigs is returned when we receive too many sigs from the
27
        // remote party in the ClosingSigs message.
28
        ErrTooManySigs = fmt.Errorf("too many sigs received")
29

30
        // ErrNoSig is returned when we receive no sig from the remote party.
31
        ErrNoSig = fmt.Errorf("no sig received")
32

33
        // ErrUnknownFinalBalance is returned if we're unable to determine the
34
        // final channel balance after a flush.
35
        ErrUnknownFinalBalance = fmt.Errorf("unknown final balance")
36

37
        // ErrRemoteCannotPay is returned if the remote party cannot pay the
38
        // pay for the fees when it sends a signature.
39
        ErrRemoteCannotPay = fmt.Errorf("remote cannot pay fees")
40

41
        // ErrNonFinalSequence is returned if we receive a non-final sequence
42
        // from the remote party for their signature.
43
        ErrNonFinalSequence = fmt.Errorf("received non-final sequence")
44

45
        // ErrCloserNoClosee is returned if our balance is dust, but the remote
46
        // party includes our output.
47
        ErrCloserNoClosee = fmt.Errorf("expected CloserNoClosee sig")
48

49
        // ErrCloserAndClosee is returned when we expect a sig covering both
50
        // outputs, it isn't present.
51
        ErrCloserAndClosee = fmt.Errorf("expected CloserAndClosee sig")
52

53
        // ErrWrongLocalScript is returned when the remote party sends a
54
        // ClosingComplete message that doesn't carry our last local script
55
        // sent.
56
        ErrWrongLocalScript = fmt.Errorf("wrong local script")
57
)
58

59
// ProtocolEvent is a special interface used to create the equivalent of a
60
// sum-type, but using a "sealed" interface. Protocol events can be used as
61
// input to trigger a state transition, and also as output to trigger a new set
62
// of events into the very same state machine.
63
type ProtocolEvent interface {
64
        protocolSealed()
65
}
66

67
// ProtocolEvents is a special type constraint that enumerates all the possible
68
// protocol events. This is used mainly as type-level documentation, and may
69
// also be useful to constraint certain state transition functions.
70
type ProtocolEvents interface {
71
        SendShutdown | ShutdownReceived | ShutdownComplete | ChannelFlushed |
72
                SendOfferEvent | OfferReceivedEvent | LocalSigReceived |
73
                SpendEvent
74
}
75

76
// SpendEvent indicates that a transaction spending the funding outpoint has
77
// been confirmed in the main chain.
78
type SpendEvent struct {
79
        // Tx is the spending transaction that has been confirmed.
80
        Tx *wire.MsgTx
81

82
        // BlockHeight is the height of the block that confirmed the
83
        // transaction.
84
        BlockHeight uint32
85
}
86

87
// protocolSealed indicates that this struct is a ProtocolEvent instance.
88
func (s *SpendEvent) protocolSealed() {}
×
89

90
// SendShutdown indicates that the user wishes to co-op close the channel, so we
91
// should send a new shutdown message to the remote party.
92
//
93
// transition:
94
//   - fromState: ChannelActive
95
//   - toState: ChannelFlushing
96
type SendShutdown struct {
97
        // DeliveryAddr is the address we'd like to receive the funds to. If
98
        // None, then a new addr will be generated.
99
        DeliveryAddr fn.Option[lnwire.DeliveryAddress]
100

101
        // IdealFeeRate is the ideal fee rate we'd like to use for the closing
102
        // attempt.
103
        IdealFeeRate chainfee.SatPerVByte
104
}
105

106
// protocolSealed indicates that this struct is a ProtocolEvent instance.
107
func (s *SendShutdown) protocolSealed() {}
×
108

109
// ShutdownReceived indicates that we received a shutdown event so we need to
110
// enter the flushing state.
111
//
112
// transition:
113
//   - fromState: ChannelActive
114
//   - toState: ChannelFlushing
115
type ShutdownReceived struct {
116
        // ShutdownScript is the script the remote party wants to use to
117
        // shutdown.
118
        ShutdownScript lnwire.DeliveryAddress
119

120
        // BlockHeight is the height at which the shutdown message was
121
        // received. This is used for channel leases to determine if a co-op
122
        // close can occur.
123
        BlockHeight uint32
124
}
125

126
// protocolSealed indicates that this struct is a ProtocolEvent instance.
127
func (s *ShutdownReceived) protocolSealed() {}
×
128

129
// ShutdownComplete is an event that indicates the channel has been fully
130
// shutdown. At this point, we'll go to the ChannelFlushing state so we can
131
// wait for all pending updates to be gone from the channel.
132
//
133
// transition:
134
//   - fromState: ShutdownPending
135
//   - toState: ChannelFlushing
136
type ShutdownComplete struct {
137
}
138

139
// protocolSealed indicates that this struct is a ProtocolEvent instance.
140
func (s *ShutdownComplete) protocolSealed() {}
×
141

142
// ShutdownBalances holds the local+remote balance once the channel has been
143
// fully flushed.
144
type ShutdownBalances struct {
145
        // LocalBalance is the local balance of the channel.
146
        LocalBalance lnwire.MilliSatoshi
147

148
        // RemoteBalance is the remote balance of the channel.
149
        RemoteBalance lnwire.MilliSatoshi
150
}
151

152
// unknownBalance is a special variable used to denote an unknown channel
153
// balance (channel not fully flushed yet).
154
var unknownBalance = ShutdownBalances{}
155

156
// ChannelFlushed is an event that indicates the channel has been fully flushed
157
// can we can now start closing negotiation.
158
//
159
// transition:
160
//   - fromState: ChannelFlushing
161
//   - toState: ClosingNegotiation
162
type ChannelFlushed struct {
163
        // FreshFlush indicates if this is the first time the channel has been
164
        // flushed, or if this is a flush as part of an RBF iteration.
165
        FreshFlush bool
166

167
        // ShutdownBalances is the balances of the channel once it has been
168
        // flushed. We tie this to the ChannelFlushed state as this may not be
169
        // the same as the starting value.
170
        ShutdownBalances
171
}
172

173
// protocolSealed indicates that this struct is a ProtocolEvent instance.
174
func (c *ChannelFlushed) protocolSealed() {}
×
175

176
// SendOfferEvent is a self-triggered event that transitions us from the
177
// LocalCloseStart state to the LocalOfferSent state. This kicks off the new
178
// signing process for the co-op close process.
179
//
180
// transition:
181
//   - fromState: LocalCloseStart
182
//   - toState: LocalOfferSent
183
type SendOfferEvent struct {
184
        // TargetFeeRate is the fee rate we'll use for the closing transaction.
185
        TargetFeeRate chainfee.SatPerVByte
186
}
187

188
// protocolSealed indicates that this struct is a ProtocolEvent instance.
189
func (s *SendOfferEvent) protocolSealed() {}
×
190

191
// LocalSigReceived is an event that indicates we've received a signature from
192
// the remote party, which signs our the co-op close transaction at our
193
// specified fee rate.
194
//
195
// transition:
196
//   - fromState: LocalOfferSent
197
//   - toState: ClosePending
198
type LocalSigReceived struct {
199
        // SigMsg is the sig message we received from the remote party.
200
        SigMsg lnwire.ClosingSig
201
}
202

203
// protocolSealed indicates that this struct is a ProtocolEvent instance.
204
func (s *LocalSigReceived) protocolSealed() {}
×
205

206
// OfferReceivedEvent is an event that indicates we've received an offer from
207
// the remote party. This applies to the RemoteCloseStart state.
208
//
209
// transition:
210
//   - fromState: RemoteCloseStart
211
//   - toState: ClosePending
212
type OfferReceivedEvent struct {
213
        // SigMsg is the signature message we received from the remote party.
214
        SigMsg lnwire.ClosingComplete
215
}
216

217
// protocolSealed indicates that this struct is a ProtocolEvent instance.
218
func (s *OfferReceivedEvent) protocolSealed() {}
×
219

220
// CloseSigner is an interface that abstracts away the details of the signing
221
// new coop close transactions.
222
type CloseSigner interface {
223
        // CreateCloseProposal creates a new co-op close proposal in the form
224
        // of a valid signature, the chainhash of the final txid, and our final
225
        // balance in the created state.
226
        CreateCloseProposal(proposedFee btcutil.Amount,
227
                localDeliveryScript []byte, remoteDeliveryScript []byte,
228
                closeOpt ...lnwallet.ChanCloseOpt,
229
        ) (
230
                input.Signature, *wire.MsgTx, btcutil.Amount, error)
231

232
        // CompleteCooperativeClose persistently "completes" the cooperative
233
        // close by producing a fully signed co-op close transaction.
234
        CompleteCooperativeClose(localSig, remoteSig input.Signature,
235
                localDeliveryScript, remoteDeliveryScript []byte,
236
                proposedFee btcutil.Amount, closeOpt ...lnwallet.ChanCloseOpt,
237
        ) (*wire.MsgTx, btcutil.Amount, error)
238
}
239

240
// ChanStateObserver is an interface used to observe state changes that occur
241
// in a channel. This can be used to figure out if we're able to send a
242
// shutdown message or not.
243
type ChanStateObserver interface {
244
        // NoDanglingUpdates returns true if there are no dangling updates in
245
        // the channel. In other words, there are no active update messages
246
        // that haven't already been covered by a commit sig.
247
        NoDanglingUpdates() bool
248

249
        // DisableIncomingAdds instructs the channel link to disable process new
250
        // incoming add messages.
251
        DisableIncomingAdds() error
252

253
        // DisableOutgoingAdds instructs the channel link to disable process
254
        // new outgoing add messages.
255
        DisableOutgoingAdds() error
256

257
        // DisableChannel attempts to disable a channel (marking it ineligible
258
        // to forward), and also sends out a network update to disable the
259
        // channel.
260
        DisableChannel() error
261

262
        // MarkCoopBroadcasted persistently marks that the channel close
263
        // transaction has been broadcast.
264
        MarkCoopBroadcasted(*wire.MsgTx, bool) error
265

266
        // MarkShutdownSent persists the given ShutdownInfo. The existence of
267
        // the ShutdownInfo represents the fact that the Shutdown message has
268
        // been sent by us and so should be re-sent on re-establish.
269
        MarkShutdownSent(deliveryAddr []byte, isInitiator bool) error
270

271
        // FinalBalances is the balances of the channel once it has been
272
        // flushed. If Some, then this indicates that the channel is now in a
273
        // state where it's always flushed, so we can accelerate the state
274
        // transitions.
275
        FinalBalances() fn.Option[ShutdownBalances]
276
}
277

278
// Environment is a set of dependencies that a state machine may need to carry
279
// out the logic for a given state transition. All fields are to be considered
280
// immutable, and will be fixed for the lifetime of the state machine.
281
type Environment struct {
282
        // ChainParams is the chain parameters for the channel.
283
        ChainParams chaincfg.Params
284

285
        // ChanPeer is the peer we're attempting to close the channel with.
286
        ChanPeer btcec.PublicKey
287

288
        // ChanPoint is the channel point of the active channel.
289
        ChanPoint wire.OutPoint
290

291
        // ChanID is the channel ID of the channel we're attempting to close.
292
        ChanID lnwire.ChannelID
293

294
        // ShortChanID is the short channel ID of the channel we're attempting
295
        // to close.
296
        Scid lnwire.ShortChannelID
297

298
        // ChanType is the type of channel we're attempting to close.
299
        ChanType channeldb.ChannelType
300

301
        // BlockHeight is the current block height.
302
        BlockHeight uint32
303

304
        // DefaultFeeRate is the fee we'll use for the closing transaction if
305
        // the user didn't specify an ideal fee rate. This may happen if the
306
        // remote party is the one that initiates the co-op close.
307
        DefaultFeeRate chainfee.SatPerVByte
308

309
        // ThawHeight is the height at which the channel will be thawed. If
310
        // this is None, then co-op close can occur at any moment.
311
        ThawHeight fn.Option[uint32]
312

313
        // RemoteUprontShutdown is the upfront shutdown addr of the remote
314
        // party. We'll use this to validate if the remote peer is authorized to
315
        // close the channel with the sent addr or not.
316
        RemoteUpfrontShutdown fn.Option[lnwire.DeliveryAddress]
317

318
        // LocalUprontShutdown is our upfront shutdown address. If Some, then
319
        // we'll default to using this.
320
        LocalUpfrontShutdown fn.Option[lnwire.DeliveryAddress]
321

322
        // NewDeliveryScript is a function that returns a new delivery script.
323
        // This is used if we don't have an upfront shutdown addr, and no addr
324
        // was specified at closing time.
325
        NewDeliveryScript func() (lnwire.DeliveryAddress, error)
326

327
        // FeeEstimator is the fee estimator we'll use to determine the fee in
328
        // satoshis we'll pay given a local and/or remote output.
329
        FeeEstimator CoopFeeEstimator
330

331
        // ChanObserver is an interface used to observe state changes to the
332
        // channel. We'll use this to figure out when/if we can send certain
333
        // messages.
334
        ChanObserver ChanStateObserver
335

336
        // CloseSigner is the signer we'll use to sign the close transaction.
337
        // This is a part of the ChannelFlushed state, as the channel state
338
        // we'll be signing can only be determined once the channel has been
339
        // flushed.
340
        CloseSigner CloseSigner
341
}
342

343
// Name returns the name of the environment. This is used to uniquely identify
344
// the environment of related state machines. For this state machine, the name
345
// is based on the channel ID.
346
func (e *Environment) Name() string {
27✔
347
        return fmt.Sprintf("rbf_chan_closer(%v)", e.ChanPoint)
27✔
348
}
27✔
349

350
// CloseStateTransition is the StateTransition type specific to the coop close
351
// state machine.
352
//
353
//nolint:ll
354
type CloseStateTransition = protofsm.StateTransition[ProtocolEvent, *Environment]
355

356
// ProtocolState is our sum-type ish interface that represents the current
357
// protocol state.
358
type ProtocolState interface {
359
        // protocolStateSealed is a special method that is used to seal the
360
        // interface (only types in this package can implement it).
361
        protocolStateSealed()
362

363
        // IsTerminal returns true if the target state is a terminal state.
364
        IsTerminal() bool
365

366
        // ProcessEvent takes a protocol event, and implements a state
367
        // transition for the state.
368
        ProcessEvent(ProtocolEvent, *Environment) (*CloseStateTransition, error)
369

370
        // String returns the name of the state.
371
        String() string
372
}
373

374
// AsymmetricPeerState is an extension of the normal ProtocolState interface
375
// that gives a caller a hit on if the target state should process an incoming
376
// event or not.
377
type AsymmetricPeerState interface {
378
        ProtocolState
379

380
        // ShouldRouteTo returns true if the target state should process the
381
        // target event.
382
        ShouldRouteTo(ProtocolEvent) bool
383
}
384

385
// ProtocolStates is a special type constraint that enumerates all the possible
386
// protocol states.
387
type ProtocolStates interface {
388
        ChannelActive | ShutdownPending | ChannelFlushing | ClosingNegotiation |
389
                LocalCloseStart | LocalOfferSent | RemoteCloseStart |
390
                ClosePending | CloseFin | CloseErr
391
}
392

393
// ChannelActive is the base state for the channel closer state machine. In
394
// this state, we haven't begun the shutdown process yet, so the channel is
395
// still active. Receiving the ShutdownSent or ShutdownReceived events will
396
// transition us to the ChannelFushing state.
397
//
398
// When we transition to this state, we emit a DaemonEvent to send the shutdown
399
// message if we received one ourselves. Alternatively, we may send out a new
400
// shutdown if we're initiating it for the very first time.
401
//
402
// transition:
403
//   - fromState: None
404
//   - toState: ChannelFlushing
405
//
406
// input events:
407
//   - SendShutdown
408
//   - ShutdownReceived
409
type ChannelActive struct {
410
}
411

412
// String returns the name of the state for ChannelActive.
NEW
413
func (c *ChannelActive) String() string {
×
NEW
414
        return "ChannelActive"
×
NEW
415
}
×
416

417
// IsTerminal returns true if the target state is a terminal state.
418
func (c *ChannelActive) IsTerminal() bool {
×
419
        return false
×
420
}
×
421

422
// protocolSealed indicates that this struct is a ProtocolEvent instance.
423
func (c *ChannelActive) protocolStateSealed() {}
×
424

425
// ShutdownScripts is a set of scripts that we'll use to co-op close the
426
// channel.
427
type ShutdownScripts struct {
428
        // LocalDeliveryScript is the script that we'll send our settled
429
        // channel funds to.
430
        LocalDeliveryScript lnwire.DeliveryAddress
431

432
        // RemoteDeliveryScript is the script that we'll send the remote
433
        // party's settled channel funds to.
434
        RemoteDeliveryScript lnwire.DeliveryAddress
435
}
436

437
// ShutdownPending is the state we enter into after we've sent or received the
438
// shutdown message. If we sent the shutdown, then we'll wait for the remote
439
// party to send a shutdown. Otherwise, if we received it, then we'll send our
440
// shutdown then go to the next state.
441
//
442
// transition:
443
//   - fromState: ChannelActive
444
//   - toState: ChannelFlushing
445
//
446
// input events:
447
//   - SendShutdown
448
//   - ShutdownReceived
449
type ShutdownPending struct {
450
        // ShutdownScripts store the set of scripts we'll use to initiate a coop
451
        // close.
452
        ShutdownScripts
453

454
        // IdealFeeRate is the ideal fee rate we'd like to use for the closing
455
        // attempt.
456
        IdealFeeRate fn.Option[chainfee.SatPerVByte]
457
}
458

459
// String returns the name of the state for ShutdownPending.
NEW
460
func (s *ShutdownPending) String() string {
×
NEW
461
        return "ShutdownPending"
×
NEW
462
}
×
463

464
// IsTerminal returns true if the target state is a terminal state.
465
func (s *ShutdownPending) IsTerminal() bool {
×
466
        return false
×
467
}
×
468

469
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
470
func (s *ShutdownPending) protocolStateSealed() {}
×
471

472
// ChannelFlushing is the state we enter into after we've received or sent a
473
// shutdown message. In this state, we wait the ChannelFlushed event, after
474
// which we'll transition to the CloseReady state.
475
//
476
// transition:
477
//   - fromState: ShutdownPending
478
//   - toState: ClosingNegotiation
479
//
480
// input events:
481
//   - ShutdownComplete
482
//   - ShutdownReceived
483
type ChannelFlushing struct {
484
        // EarlyRemoteOffer is the offer we received from the remote party
485
        // before we obtained the local channel flushed event. We'll stash this
486
        // to process later.
487
        EarlyRemoteOffer fn.Option[OfferReceivedEvent]
488

489
        // ShutdownScripts store the set of scripts we'll use to initiate a coop
490
        // close.
491
        ShutdownScripts
492

493
        // IdealFeeRate is the ideal fee rate we'd like to use for the closing
494
        // transaction. Once the channel has been flushed, we'll use this as
495
        // our target fee rate.
496
        IdealFeeRate fn.Option[chainfee.SatPerVByte]
497
}
498

499
// String returns the name of the state for ChannelFlushing.
NEW
500
func (c *ChannelFlushing) String() string {
×
NEW
501
        return "ChannelFlushing"
×
NEW
502
}
×
503

504
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
505
func (c *ChannelFlushing) protocolStateSealed() {}
×
506

507
// IsTerminal returns true if the target state is a terminal state.
508
func (c *ChannelFlushing) IsTerminal() bool {
×
509
        return false
×
510
}
×
511

512
// ClosingNegotiation is the state we transition to once the channel has been
513
// flushed. This is actually a composite state that contains one for each side
514
// of the channel, as the closing process is asymmetric. Once either of the
515
// peer states reaches the CloseFin state, then the channel is fully closed,
516
// and we'll transition to that terminal state.
517
//
518
// transition:
519
//   - fromState: ChannelFlushing
520
//   - toState: CloseFin
521
//
522
// input events:
523
//   - ChannelFlushed
524
type ClosingNegotiation struct {
525
        // PeerStates is a composite state that contains the state for both the
526
        // local and remote parties. Our usage of Dual makes this a special
527
        // state that allows us to treat two states as a single state. We'll use
528
        // the ShouldRouteTo method to determine which state route incoming
529
        // events to.
530
        PeerState lntypes.Dual[AsymmetricPeerState]
531

532
        // CloseChannelTerms is the terms we'll use to close the channel. We
533
        // hold a value here which is pointed to by the various
534
        // AsymmetricPeerState instances. This allows us to update this value if
535
        // the remote peer sends a new address, with each of the state noting
536
        // the new value via a pointer.
537
        *CloseChannelTerms
538
}
539

540
// String returns the name of the state for ClosingNegotiation.
NEW
541
func (c *ClosingNegotiation) String() string {
×
NEW
542
        localState := c.PeerState.GetForParty(lntypes.Local)
×
NEW
543
        remoteState := c.PeerState.GetForParty(lntypes.Remote)
×
NEW
544

×
NEW
545
        return fmt.Sprintf("ClosingNegotiation(local=%v, remote=%v)",
×
NEW
546
                localState, remoteState)
×
UNCOV
547
}
×
548

549
// IsTerminal returns true if the target state is a terminal state.
550
func (c *ClosingNegotiation) IsTerminal() bool {
×
551
        return false
×
552
}
×
553

554
// protocolSealed indicates that this struct is a ProtocolEvent instance.
555
func (c *ClosingNegotiation) protocolStateSealed() {}
×
556

557
// ErrState can be used to introspect into a benign error related to a state
558
// transition.
559
type ErrState interface {
560
        sealed()
561

562
        error
563

564
        // Err returns an error for the ErrState.
565
        Err() error
566
}
567

568
// ErrStateCantPayForFee is sent when the local party attempts a fee update
569
// that they can't actually party for.
570
type ErrStateCantPayForFee struct {
571
        localBalance btcutil.Amount
572

573
        attemptedFee btcutil.Amount
574
}
575

576
// NewErrStateCantPayForFee returns a new NewErrStateCantPayForFee error.
577
func NewErrStateCantPayForFee(localBalance, attemptedFee btcutil.Amount,
NEW
578
) *ErrStateCantPayForFee {
×
NEW
579

×
NEW
580
        return &ErrStateCantPayForFee{
×
NEW
581
                localBalance: localBalance,
×
NEW
582
                attemptedFee: attemptedFee,
×
NEW
583
        }
×
NEW
584
}
×
585

586
// sealed makes this a sealed interface.
NEW
587
func (e *ErrStateCantPayForFee) sealed() {
×
NEW
588
}
×
589

590
// Err returns an error for the ErrState.
NEW
591
func (e *ErrStateCantPayForFee) Err() error {
×
NEW
592
        return fmt.Errorf("cannot pay for fee of %v, only have %v local "+
×
NEW
593
                "balance", e.attemptedFee, e.localBalance)
×
NEW
594
}
×
595

596
// Error returns the error string for the ErrState.
NEW
597
func (e *ErrStateCantPayForFee) Error() string {
×
NEW
598
        return e.Err().Error()
×
NEW
599
}
×
600

601
// CloseChannelTerms is a set of terms that we'll use to close the channel. This
602
// includes the balances of the channel, and the scripts we'll use to send each
603
// party's funds to.
604
type CloseChannelTerms struct {
605
        ShutdownScripts
606

607
        ShutdownBalances
608
}
609

610
// DeriveCloseTxOuts takes the close terms, and returns the local and remote tx
611
// out for the close transaction. If an output is dust, then it'll be nil.
612
func (c *CloseChannelTerms) DeriveCloseTxOuts() (*wire.TxOut, *wire.TxOut) {
12✔
613
        //nolint:ll
12✔
614
        deriveTxOut := func(balance btcutil.Amount, pkScript []byte) *wire.TxOut {
36✔
615
                // We'll base the existence of the output on our normal dust
24✔
616
                // check.
24✔
617
                dustLimit := lnwallet.DustLimitForSize(len(pkScript))
24✔
618
                if balance >= dustLimit {
47✔
619
                        return &wire.TxOut{
23✔
620
                                PkScript: pkScript,
23✔
621
                                Value:    int64(balance),
23✔
622
                        }
23✔
623
                }
23✔
624

625
                return nil
1✔
626
        }
627

628
        localTxOut := deriveTxOut(
12✔
629
                c.LocalBalance.ToSatoshis(),
12✔
630
                c.LocalDeliveryScript,
12✔
631
        )
12✔
632
        remoteTxOut := deriveTxOut(
12✔
633
                c.RemoteBalance.ToSatoshis(),
12✔
634
                c.RemoteDeliveryScript,
12✔
635
        )
12✔
636

12✔
637
        return localTxOut, remoteTxOut
12✔
638
}
639

640
// RemoteAmtIsDust returns true if the remote output is dust.
641
func (c *CloseChannelTerms) RemoteAmtIsDust() bool {
×
642
        return c.RemoteBalance.ToSatoshis() < lnwallet.DustLimitForSize(
×
643
                len(c.RemoteDeliveryScript),
×
644
        )
×
645
}
×
646

647
// LocalAmtIsDust returns true if the local output is dust.
648
func (c *CloseChannelTerms) LocalAmtIsDust() bool {
5✔
649
        return c.LocalBalance.ToSatoshis() < lnwallet.DustLimitForSize(
5✔
650
                len(c.LocalDeliveryScript),
5✔
651
        )
5✔
652
}
5✔
653

654
// LocalCanPayFees returns true if the local party can pay the absolute fee
655
// from their local settled balance.
656
func (c *CloseChannelTerms) LocalCanPayFees(absoluteFee btcutil.Amount) bool {
12✔
657
        return c.LocalBalance.ToSatoshis() >= absoluteFee
12✔
658
}
12✔
659

660
// RemoteCanPayFees returns true if the remote party can pay the absolute fee
661
// from their remote settled balance.
662
func (c *CloseChannelTerms) RemoteCanPayFees(absoluteFee btcutil.Amount) bool {
6✔
663
        return c.RemoteBalance.ToSatoshis() >= absoluteFee
6✔
664
}
6✔
665

666
// LocalCloseStart is the state we enter into after we've received or sent
667
// shutdown, and the channel has been flushed. In this state, we'll emit a new
668
// event to send our offer to drive the rest of the process.
669
//
670
// transition:
671
//   - fromState: ChannelFlushing
672
//   - toState: LocalOfferSent
673
//
674
// input events:
675
//   - SendOfferEvent
676
type LocalCloseStart struct {
677
        *CloseChannelTerms
678
}
679

680
// String returns the name of the state for LocalCloseStart, including proposed
681
// fee details.
NEW
682
func (l *LocalCloseStart) String() string {
×
NEW
683
        return "LocalCloseStart"
×
UNCOV
684
}
×
685

686
// ShouldRouteTo returns true if the target state should process the target
687
// event.
688
func (l *LocalCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
15✔
689
        switch event.(type) {
15✔
690
        case *SendOfferEvent:
8✔
691
                return true
8✔
692
        default:
7✔
693
                return false
7✔
694
        }
695
}
696

697
// IsTerminal returns true if the target state is a terminal state.
698
func (l *LocalCloseStart) IsTerminal() bool {
×
699
        return false
×
700
}
×
701

702
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
703
func (l *LocalCloseStart) protocolStateSealed() {}
×
704

705
// LocalOfferSent is the state we transition to after we reveiver the
706
// SendOfferEvent in the LocalCloseStart state. With this state we send our
707
// offer to the remote party, then await a sig from them which concludes the
708
// local cooperative close process.
709
//
710
// transition:
711
//   - fromState: LocalCloseStart
712
//   - toState: ClosePending
713
//
714
// input events:
715
//   - LocalSigReceived
716
type LocalOfferSent struct {
717
        *CloseChannelTerms
718

719
        // ProposedFee is the fee we proposed to the remote party.
720
        ProposedFee btcutil.Amount
721

722
        // ProposedFeeRate is the fee rate we proposed to the remote party.
723
        ProposedFeeRate chainfee.SatPerVByte
724

725
        // LocalSig is the signature we sent to the remote party.
726
        LocalSig lnwire.Sig
727
}
728

729
// String returns the name of the state for LocalOfferSent, including proposed
730

NEW
731
func (l *LocalOfferSent) String() string {
×
NEW
732
        return fmt.Sprintf("LocalOfferSent(proposed_fee=%v, "+
×
NEW
733
                "proposed_fee_rate=%v)", l.ProposedFee, l.ProposedFee)
×
NEW
734
}
×
735

736
// ShouldRouteTo returns true if the target state should process the target
737
// event.
738
func (l *LocalOfferSent) ShouldRouteTo(event ProtocolEvent) bool {
4✔
739
        switch event.(type) {
4✔
740
        case *LocalSigReceived:
4✔
741
                return true
4✔
742
        default:
×
743
                return false
×
744
        }
745
}
746

747
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
748
func (l *LocalOfferSent) protocolStateSealed() {}
×
749

750
// IsTerminal returns true if the target state is a terminal state.
751
func (l *LocalOfferSent) IsTerminal() bool {
×
752
        return false
×
753
}
×
754

755
// ClosePending is the state we enter after concluding the negotiation for the
756
// remote or local state. At this point, given a confirmation notification we
757
// can terminate the process. Otherwise, we can receive a fresh CoopCloseReq to
758
// go back to the very start.
759
//
760
// transition:
761
//   - fromState: LocalOfferSent || RemoteCloseStart
762
//   - toState: CloseFin
763
//
764
// input events:
765
//   - LocalSigReceived
766
//   - OfferReceivedEvent
767
type ClosePending struct {
768
        // CloseTx is the pending close transaction.
769
        CloseTx *wire.MsgTx
770

771
        *CloseChannelTerms
772

773
        // FeeRate is the fee rate of the closing transaction.
774
        FeeRate chainfee.SatPerVByte
775

776
        // Party indicates which party is at this state. This is used to
777
        // implement the state transition properly, based on ShouldRouteTo.
778
        Party lntypes.ChannelParty
779
}
780

781
// String returns the name of the state for ClosePending.
NEW
782
func (c *ClosePending) String() string {
×
NEW
783
        return fmt.Sprintf("ClosePending(party=%v, fee_rate=%v)",
×
NEW
784
                c.Party, c.FeeRate)
×
NEW
785
}
×
786

787
// isType returns true if the value is of type T.
788
func isType[T any](value any) bool {
2✔
789
        _, ok := value.(T)
2✔
790
        return ok
2✔
791
}
2✔
792

793
// ShouldRouteTo returns true if the target state should process the target
794
// event.
795
func (c *ClosePending) ShouldRouteTo(event ProtocolEvent) bool {
2✔
796
        switch event.(type) {
2✔
797
        case *SpendEvent:
×
798
                return true
×
799
        default:
2✔
800
                switch {
2✔
801
                case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
1✔
802
                        return true
1✔
803

804
                case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
805
                        event,
806
                ):
1✔
807

1✔
808
                        return true
1✔
809
                }
810

UNCOV
811
                return false
×
812
        }
813
}
814

815
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
816
func (c *ClosePending) protocolStateSealed() {}
×
817

818
// IsTerminal returns true if the target state is a terminal state.
819
func (c *ClosePending) IsTerminal() bool {
×
820
        return true
×
821
}
×
822

823
// CloseFin is the terminal state for the channel closer state machine. At this
824
// point, the close tx has been confirmed on chain.
825
type CloseFin struct {
826
        // ConfirmedTx is the transaction that confirmed the channel close.
827
        ConfirmedTx *wire.MsgTx
828
}
829

830
// String returns the name of the state for CloseFin.
NEW
831
func (c *CloseFin) String() string {
×
NEW
832
        return "CloseFin"
×
NEW
833
}
×
834

835
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
836
func (c *CloseFin) protocolStateSealed() {}
×
837

838
// IsTerminal returns true if the target state is a terminal state.
839
func (c *CloseFin) IsTerminal() bool {
×
840
        return true
×
841
}
×
842

843
// RemoteCloseStart is similar to the LocalCloseStart, but is used to drive the
844
// process of signing an offer for the remote party
845
//
846
// transition:
847
//   - fromState: ChannelFlushing
848
//   - toState: ClosePending
849
type RemoteCloseStart struct {
850
        *CloseChannelTerms
851
}
852

853
// String returns the name of the state for RemoteCloseStart.
NEW
854
func (r *RemoteCloseStart) String() string {
×
NEW
855
        return "RemoteCloseStart"
×
UNCOV
856
}
×
857

858
// ShouldRouteTo returns true if the target state should process the target
859
// event.
860
func (l *RemoteCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
6✔
861
        switch event.(type) {
6✔
862
        case *OfferReceivedEvent:
6✔
863
                return true
6✔
864
        default:
×
865
                return false
×
866
        }
867
}
868

869
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
870
func (l *RemoteCloseStart) protocolStateSealed() {}
×
871

872
// IsTerminal returns true if the target state is a terminal state.
873
func (l *RemoteCloseStart) IsTerminal() bool {
×
874
        return false
×
875
}
×
876

877
// CloseErr is an error state in the protocol. We enter this state when a
878
// protocol constraint is violated, or an upfront sanity check fails.
879
type CloseErr struct {
880
        ErrState
881

882
        *CloseChannelTerms
883

884
        // Party indicates which party is at this state. This is used to
885
        // implement the state transition properly, based on ShouldRouteTo.
886
        Party lntypes.ChannelParty
887
}
888

889
// String returns the name of the state for CloseErr, including error and party
890
// details.
NEW
891
func (c *CloseErr) String() string {
×
NEW
892
        return fmt.Sprintf("CloseErr(Party: %v, Error: %v)", c.Party, c.Err())
×
NEW
893
}
×
894

895
// ShouldRouteTo returns true if the target state should process the target
896
// event.
NEW
897
func (c *CloseErr) ShouldRouteTo(event ProtocolEvent) bool {
×
NEW
898
        switch event.(type) {
×
NEW
899
        case *SpendEvent:
×
NEW
900
                return true
×
NEW
901
        default:
×
NEW
902
                switch {
×
NEW
903
                case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
×
NEW
904
                        return true
×
905

906
                case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
907
                        event,
NEW
908
                ):
×
NEW
909

×
NEW
910
                        return true
×
911
                }
912

NEW
913
                return false
×
914
        }
915
}
916

917
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
NEW
918
func (c *CloseErr) protocolStateSealed() {}
×
919

920
// IsTerminal returns true if the target state is a terminal state.
NEW
921
func (c *CloseErr) IsTerminal() bool {
×
NEW
922
        return true
×
NEW
923
}
×
924

925
// RbfChanCloser is a state machine that handles the RBF-enabled cooperative
926
// channel close protocol.
927
type RbfChanCloser = protofsm.StateMachine[ProtocolEvent, *Environment]
928

929
// RbfChanCloserCfg is a configuration struct that is used to initialize a new
930
// RBF chan closer state machine.
931
type RbfChanCloserCfg = protofsm.StateMachineCfg[ProtocolEvent, *Environment]
932

933
// RbfSpendMapper is a type used to map the generic spend event to one specific
934
// to this package.
935
type RbfSpendMapper = protofsm.SpendMapper[ProtocolEvent]
936

937
func SpendMapper(spendEvent *chainntnfs.SpendDetail) ProtocolEvent {
×
938
        return &SpendEvent{
×
939
                Tx:          spendEvent.SpendingTx,
×
940
                BlockHeight: uint32(spendEvent.SpendingHeight),
×
941
        }
×
942
}
×
943

944
// RbfMsgMapperT is a type used to map incoming wire messages to protocol
945
// events.
946
type RbfMsgMapperT = protofsm.MsgMapper[ProtocolEvent]
947

948
// RbfState is a type alias for the state of the RBF channel closer.
949
type RbfState = protofsm.State[ProtocolEvent, *Environment]
950

951
// RbfEvent is a type alias for the event type of the RBF channel closer.
952
type RbfEvent = protofsm.EmittedEvent[ProtocolEvent]
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