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

lightningnetwork / lnd / 13558005087

27 Feb 2025 03:04AM UTC coverage: 58.834% (-0.001%) from 58.835%
13558005087

Pull #8453

github

Roasbeef
lnwallet/chancloser: increase test coverage of state machine
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

1079 of 1370 new or added lines in 23 files covered. (78.76%)

578 existing lines in 40 files now uncovered.

137063 of 232965 relevant lines covered (58.83%)

19205.84 hits per line

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

67.53
/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 {
42✔
347
        return fmt.Sprintf("rbf_chan_closer(%v)", e.ChanPoint)
42✔
348
}
42✔
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.
413
func (c *ChannelActive) String() string {
1✔
414
        return "ChannelActive"
1✔
415
}
1✔
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
        // EarlyRemoteOffer is the offer we received from the remote party
459
        // before we received their shutdown message. We'll stash it to process
460
        // later.
461
        EarlyRemoteOffer fn.Option[OfferReceivedEvent]
462
}
463

464
// String returns the name of the state for ShutdownPending.
465
func (s *ShutdownPending) String() string {
1✔
466
        return "ShutdownPending"
1✔
467
}
1✔
468

469
// IsTerminal returns true if the target state is a terminal state.
470
func (s *ShutdownPending) IsTerminal() bool {
×
471
        return false
×
472
}
×
473

474
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
475
func (s *ShutdownPending) protocolStateSealed() {}
×
476

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

494
        // ShutdownScripts store the set of scripts we'll use to initiate a coop
495
        // close.
496
        ShutdownScripts
497

498
        // IdealFeeRate is the ideal fee rate we'd like to use for the closing
499
        // transaction. Once the channel has been flushed, we'll use this as
500
        // our target fee rate.
501
        IdealFeeRate fn.Option[chainfee.SatPerVByte]
502
}
503

504
// String returns the name of the state for ChannelFlushing.
505
func (c *ChannelFlushing) String() string {
1✔
506
        return "ChannelFlushing"
1✔
507
}
1✔
508

509
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
510
func (c *ChannelFlushing) protocolStateSealed() {}
×
511

512
// IsTerminal returns true if the target state is a terminal state.
513
func (c *ChannelFlushing) IsTerminal() bool {
×
514
        return false
×
515
}
×
516

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

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

545
// String returns the name of the state for ClosingNegotiation.
546
func (c *ClosingNegotiation) String() string {
3✔
547
        localState := c.PeerState.GetForParty(lntypes.Local)
3✔
548
        remoteState := c.PeerState.GetForParty(lntypes.Remote)
3✔
549

3✔
550
        return fmt.Sprintf("ClosingNegotiation(local=%v, remote=%v)",
3✔
551
                localState, remoteState)
3✔
552
}
3✔
553

554
// IsTerminal returns true if the target state is a terminal state.
555
func (c *ClosingNegotiation) IsTerminal() bool {
×
556
        return false
×
557
}
×
558

559
// protocolSealed indicates that this struct is a ProtocolEvent instance.
560
func (c *ClosingNegotiation) protocolStateSealed() {}
×
561

562
// ErrState can be used to introspect into a benign error related to a state
563
// transition.
564
type ErrState interface {
565
        sealed()
566

567
        error
568

569
        // Err returns an error for the ErrState.
570
        Err() error
571
}
572

573
// ErrStateCantPayForFee is sent when the local party attempts a fee update
574
// that they can't actually party for.
575
type ErrStateCantPayForFee struct {
576
        localBalance btcutil.Amount
577

578
        attemptedFee btcutil.Amount
579
}
580

581
// NewErrStateCantPayForFee returns a new NewErrStateCantPayForFee error.
582
func NewErrStateCantPayForFee(localBalance, attemptedFee btcutil.Amount,
583
) *ErrStateCantPayForFee {
2✔
584

2✔
585
        return &ErrStateCantPayForFee{
2✔
586
                localBalance: localBalance,
2✔
587
                attemptedFee: attemptedFee,
2✔
588
        }
2✔
589
}
2✔
590

591
// sealed makes this a sealed interface.
NEW
592
func (e *ErrStateCantPayForFee) sealed() {
×
NEW
593
}
×
594

595
// Err returns an error for the ErrState.
596
func (e *ErrStateCantPayForFee) Err() error {
1✔
597
        return fmt.Errorf("cannot pay for fee of %v, only have %v local "+
1✔
598
                "balance", e.attemptedFee, e.localBalance)
1✔
599
}
1✔
600

601
// Error returns the error string for the ErrState.
602
func (e *ErrStateCantPayForFee) Error() string {
1✔
603
        return e.Err().Error()
1✔
604
}
1✔
605

606
// CloseChannelTerms is a set of terms that we'll use to close the channel. This
607
// includes the balances of the channel, and the scripts we'll use to send each
608
// party's funds to.
609
type CloseChannelTerms struct {
610
        ShutdownScripts
611

612
        ShutdownBalances
613
}
614

615
// DeriveCloseTxOuts takes the close terms, and returns the local and remote tx
616
// out for the close transaction. If an output is dust, then it'll be nil.
617
func (c *CloseChannelTerms) DeriveCloseTxOuts() (*wire.TxOut, *wire.TxOut) {
15✔
618
        //nolint:ll
15✔
619
        deriveTxOut := func(balance btcutil.Amount, pkScript []byte) *wire.TxOut {
44✔
620
                // We'll base the existence of the output on our normal dust
29✔
621
                // check.
29✔
622
                dustLimit := lnwallet.DustLimitForSize(len(pkScript))
29✔
623
                if balance >= dustLimit {
57✔
624
                        return &wire.TxOut{
28✔
625
                                PkScript: pkScript,
28✔
626
                                Value:    int64(balance),
28✔
627
                        }
28✔
628
                }
28✔
629

630
                return nil
2✔
631
        }
632

633
        localTxOut := deriveTxOut(
15✔
634
                c.LocalBalance.ToSatoshis(),
15✔
635
                c.LocalDeliveryScript,
15✔
636
        )
15✔
637
        remoteTxOut := deriveTxOut(
15✔
638
                c.RemoteBalance.ToSatoshis(),
15✔
639
                c.RemoteDeliveryScript,
15✔
640
        )
15✔
641

15✔
642
        return localTxOut, remoteTxOut
15✔
643
}
644

645
// RemoteAmtIsDust returns true if the remote output is dust.
646
func (c *CloseChannelTerms) RemoteAmtIsDust() bool {
×
647
        return c.RemoteBalance.ToSatoshis() < lnwallet.DustLimitForSize(
×
648
                len(c.RemoteDeliveryScript),
×
649
        )
×
650
}
×
651

652
// LocalAmtIsDust returns true if the local output is dust.
653
func (c *CloseChannelTerms) LocalAmtIsDust() bool {
7✔
654
        return c.LocalBalance.ToSatoshis() < lnwallet.DustLimitForSize(
7✔
655
                len(c.LocalDeliveryScript),
7✔
656
        )
7✔
657
}
7✔
658

659
// LocalCanPayFees returns true if the local party can pay the absolute fee
660
// from their local settled balance.
661
func (c *CloseChannelTerms) LocalCanPayFees(absoluteFee btcutil.Amount) bool {
15✔
662
        return c.LocalBalance.ToSatoshis() >= absoluteFee
15✔
663
}
15✔
664

665
// RemoteCanPayFees returns true if the remote party can pay the absolute fee
666
// from their remote settled balance.
667
func (c *CloseChannelTerms) RemoteCanPayFees(absoluteFee btcutil.Amount) bool {
8✔
668
        return c.RemoteBalance.ToSatoshis() >= absoluteFee
8✔
669
}
8✔
670

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

685
// String returns the name of the state for LocalCloseStart, including proposed
686
// fee details.
687
func (l *LocalCloseStart) String() string {
3✔
688
        return "LocalCloseStart"
3✔
689
}
3✔
690

691
// ShouldRouteTo returns true if the target state should process the target
692
// event.
693
func (l *LocalCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
20✔
694
        switch event.(type) {
20✔
695
        case *SendOfferEvent:
11✔
696
                return true
11✔
697
        default:
10✔
698
                return false
10✔
699
        }
700
}
701

702
// IsTerminal returns true if the target state is a terminal state.
703
func (l *LocalCloseStart) IsTerminal() bool {
×
704
        return false
×
705
}
×
706

707
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
708
func (l *LocalCloseStart) protocolStateSealed() {}
×
709

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

724
        // ProposedFee is the fee we proposed to the remote party.
725
        ProposedFee btcutil.Amount
726

727
        // ProposedFeeRate is the fee rate we proposed to the remote party.
728
        ProposedFeeRate chainfee.SatPerVByte
729

730
        // LocalSig is the signature we sent to the remote party.
731
        LocalSig lnwire.Sig
732
}
733

734
// String returns the name of the state for LocalOfferSent, including proposed
735

736
func (l *LocalOfferSent) String() string {
1✔
737
        return fmt.Sprintf("LocalOfferSent(proposed_fee=%v, "+
1✔
738
                "proposed_fee_rate=%v)", l.ProposedFee, l.ProposedFee)
1✔
739
}
1✔
740

741
// ShouldRouteTo returns true if the target state should process the target
742
// event.
743
func (l *LocalOfferSent) ShouldRouteTo(event ProtocolEvent) bool {
6✔
744
        switch event.(type) {
6✔
745
        case *LocalSigReceived:
6✔
746
                return true
6✔
747
        default:
1✔
748
                return false
1✔
749
        }
750
}
751

752
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
753
func (l *LocalOfferSent) protocolStateSealed() {}
×
754

755
// IsTerminal returns true if the target state is a terminal state.
756
func (l *LocalOfferSent) IsTerminal() bool {
×
757
        return false
×
758
}
×
759

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

776
        *CloseChannelTerms
777

778
        // FeeRate is the fee rate of the closing transaction.
779
        FeeRate chainfee.SatPerVByte
780

781
        // Party indicates which party is at this state. This is used to
782
        // implement the state transition properly, based on ShouldRouteTo.
783
        Party lntypes.ChannelParty
784
}
785

786
// String returns the name of the state for ClosePending.
787
func (c *ClosePending) String() string {
1✔
788
        return fmt.Sprintf("ClosePending(party=%v, fee_rate=%v)",
1✔
789
                c.Party, c.FeeRate)
1✔
790
}
1✔
791

792
// isType returns true if the value is of type T.
793
func isType[T any](value any) bool {
5✔
794
        _, ok := value.(T)
5✔
795
        return ok
5✔
796
}
5✔
797

798
// ShouldRouteTo returns true if the target state should process the target
799
// event.
800
func (c *ClosePending) ShouldRouteTo(event ProtocolEvent) bool {
3✔
801
        switch event.(type) {
3✔
802
        case *SpendEvent:
×
803
                return true
×
804
        default:
3✔
805
                switch {
3✔
806
                case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
2✔
807
                        return true
2✔
808

809
                case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
810
                        event,
811
                ):
2✔
812

2✔
813
                        return true
2✔
814
                }
815

816
                return false
1✔
817
        }
818
}
819

820
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
821
func (c *ClosePending) protocolStateSealed() {}
×
822

823
// IsTerminal returns true if the target state is a terminal state.
824
func (c *ClosePending) IsTerminal() bool {
×
825
        return true
×
826
}
×
827

828
// CloseFin is the terminal state for the channel closer state machine. At this
829
// point, the close tx has been confirmed on chain.
830
type CloseFin struct {
831
        // ConfirmedTx is the transaction that confirmed the channel close.
832
        ConfirmedTx *wire.MsgTx
833
}
834

835
// String returns the name of the state for CloseFin.
836
func (c *CloseFin) String() string {
1✔
837
        return "CloseFin"
1✔
838
}
1✔
839

840
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
841
func (c *CloseFin) protocolStateSealed() {}
×
842

843
// IsTerminal returns true if the target state is a terminal state.
844
func (c *CloseFin) IsTerminal() bool {
×
845
        return true
×
846
}
×
847

848
// RemoteCloseStart is similar to the LocalCloseStart, but is used to drive the
849
// process of signing an offer for the remote party
850
//
851
// transition:
852
//   - fromState: ChannelFlushing
853
//   - toState: ClosePending
854
type RemoteCloseStart struct {
855
        *CloseChannelTerms
856
}
857

858
// String returns the name of the state for RemoteCloseStart.
859
func (r *RemoteCloseStart) String() string {
2✔
860
        return "RemoteCloseStart"
2✔
861
}
2✔
862

863
// ShouldRouteTo returns true if the target state should process the target
864
// event.
865
func (l *RemoteCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
9✔
866
        switch event.(type) {
9✔
867
        case *OfferReceivedEvent:
8✔
868
                return true
8✔
869
        default:
1✔
870
                return false
1✔
871
        }
872
}
873

874
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
875
func (l *RemoteCloseStart) protocolStateSealed() {}
×
876

877
// IsTerminal returns true if the target state is a terminal state.
878
func (l *RemoteCloseStart) IsTerminal() bool {
×
879
        return false
×
880
}
×
881

882
// CloseErr is an error state in the protocol. We enter this state when a
883
// protocol constraint is violated, or an upfront sanity check fails.
884
type CloseErr struct {
885
        ErrState
886

887
        *CloseChannelTerms
888

889
        // Party indicates which party is at this state. This is used to
890
        // implement the state transition properly, based on ShouldRouteTo.
891
        Party lntypes.ChannelParty
892
}
893

894
// String returns the name of the state for CloseErr, including error and party
895
// details.
NEW
896
func (c *CloseErr) String() string {
×
NEW
897
        return fmt.Sprintf("CloseErr(Party: %v, Error: %v)", c.Party, c.Err())
×
NEW
898
}
×
899

900
// ShouldRouteTo returns true if the target state should process the target
901
// event.
902
func (c *CloseErr) ShouldRouteTo(event ProtocolEvent) bool {
2✔
903
        switch event.(type) {
2✔
NEW
904
        case *SpendEvent:
×
NEW
905
                return true
×
906
        default:
2✔
907
                switch {
2✔
908
                case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
1✔
909
                        return true
1✔
910

911
                case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
912
                        event,
913
                ):
1✔
914

1✔
915
                        return true
1✔
916
                }
917

NEW
918
                return false
×
919
        }
920
}
921

922
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
NEW
923
func (c *CloseErr) protocolStateSealed() {}
×
924

925
// IsTerminal returns true if the target state is a terminal state.
NEW
926
func (c *CloseErr) IsTerminal() bool {
×
NEW
927
        return true
×
NEW
928
}
×
929

930
// RbfChanCloser is a state machine that handles the RBF-enabled cooperative
931
// channel close protocol.
932
type RbfChanCloser = protofsm.StateMachine[ProtocolEvent, *Environment]
933

934
// RbfChanCloserCfg is a configuration struct that is used to initialize a new
935
// RBF chan closer state machine.
936
type RbfChanCloserCfg = protofsm.StateMachineCfg[ProtocolEvent, *Environment]
937

938
// RbfSpendMapper is a type used to map the generic spend event to one specific
939
// to this package.
940
type RbfSpendMapper = protofsm.SpendMapper[ProtocolEvent]
941

942
func SpendMapper(spendEvent *chainntnfs.SpendDetail) ProtocolEvent {
1✔
943
        return &SpendEvent{
1✔
944
                Tx:          spendEvent.SpendingTx,
1✔
945
                BlockHeight: uint32(spendEvent.SpendingHeight),
1✔
946
        }
1✔
947
}
1✔
948

949
// RbfMsgMapperT is a type used to map incoming wire messages to protocol
950
// events.
951
type RbfMsgMapperT = protofsm.MsgMapper[ProtocolEvent]
952

953
// RbfState is a type alias for the state of the RBF channel closer.
954
type RbfState = protofsm.State[ProtocolEvent, *Environment]
955

956
// RbfEvent is a type alias for the event type of the RBF channel closer.
957
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