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

lightningnetwork / lnd / 15561477203

10 Jun 2025 01:54PM UTC coverage: 58.351% (-10.1%) from 68.487%
15561477203

Pull #9356

github

web-flow
Merge 6440b25db into c6d6d4c0b
Pull Request #9356: lnrpc: add incoming/outgoing channel ids filter to forwarding history request

33 of 36 new or added lines in 2 files covered. (91.67%)

28366 existing lines in 455 files now uncovered.

97715 of 167461 relevant lines covered (58.35%)

1.81 hits per line

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

60.41
/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 {
3✔
347
        return fmt.Sprintf("rbf_chan_closer(%v)", e.ChanPoint)
3✔
348
}
3✔
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 {
3✔
414
        return "ChannelActive"
3✔
415
}
3✔
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 {
3✔
466
        return "ShutdownPending"
3✔
467
}
3✔
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 {
3✔
506
        return "ChannelFlushing"
3✔
507
}
3✔
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 {
3✔
584

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

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

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

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

606
// String returns the string for the ErrStateCantPayForFee.
607
func (e *ErrStateCantPayForFee) String() string {
×
608
        return fmt.Sprintf("ErrStateCantPayForFee(local_balance=%v, "+
×
609
                "attempted_fee=%v)", e.localBalance, e.attemptedFee)
×
610
}
×
611

612
// CloseChannelTerms is a set of terms that we'll use to close the channel. This
613
// includes the balances of the channel, and the scripts we'll use to send each
614
// party's funds to.
615
type CloseChannelTerms struct {
616
        ShutdownScripts
617

618
        ShutdownBalances
619
}
620

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

636
                return nil
3✔
637
        }
638

639
        localTxOut := deriveTxOut(
3✔
640
                c.LocalBalance.ToSatoshis(),
3✔
641
                c.LocalDeliveryScript,
3✔
642
        )
3✔
643
        remoteTxOut := deriveTxOut(
3✔
644
                c.RemoteBalance.ToSatoshis(),
3✔
645
                c.RemoteDeliveryScript,
3✔
646
        )
3✔
647

3✔
648
        return localTxOut, remoteTxOut
3✔
649
}
650

651
// RemoteAmtIsDust returns true if the remote output is dust.
652
func (c *CloseChannelTerms) RemoteAmtIsDust() bool {
×
653
        return c.RemoteBalance.ToSatoshis() < lnwallet.DustLimitForSize(
×
654
                len(c.RemoteDeliveryScript),
×
655
        )
×
656
}
×
657

658
// LocalAmtIsDust returns true if the local output is dust.
659
func (c *CloseChannelTerms) LocalAmtIsDust() bool {
3✔
660
        return c.LocalBalance.ToSatoshis() < lnwallet.DustLimitForSize(
3✔
661
                len(c.LocalDeliveryScript),
3✔
662
        )
3✔
663
}
3✔
664

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

671
// RemoteCanPayFees returns true if the remote party can pay the absolute fee
672
// from their remote settled balance.
673
func (c *CloseChannelTerms) RemoteCanPayFees(absoluteFee btcutil.Amount) bool {
3✔
674
        return c.RemoteBalance.ToSatoshis() >= absoluteFee
3✔
675
}
3✔
676

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

691
// String returns the name of the state for LocalCloseStart, including proposed
692
// fee details.
693
func (l *LocalCloseStart) String() string {
3✔
694
        return "LocalCloseStart"
3✔
695
}
3✔
696

697
// ShouldRouteTo returns true if the target state should process the target
698
// event.
699
func (l *LocalCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
3✔
700
        switch event.(type) {
3✔
701
        case *SendOfferEvent:
3✔
702
                return true
3✔
703
        default:
3✔
704
                return false
3✔
705
        }
706
}
707

708
// IsTerminal returns true if the target state is a terminal state.
709
func (l *LocalCloseStart) IsTerminal() bool {
×
710
        return false
×
711
}
×
712

713
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
714
func (l *LocalCloseStart) protocolStateSealed() {}
×
715

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

730
        // ProposedFee is the fee we proposed to the remote party.
731
        ProposedFee btcutil.Amount
732

733
        // ProposedFeeRate is the fee rate we proposed to the remote party.
734
        ProposedFeeRate chainfee.SatPerVByte
735

736
        // LocalSig is the signature we sent to the remote party.
737
        LocalSig lnwire.Sig
738
}
739

740
// String returns the name of the state for LocalOfferSent, including proposed.
741
func (l *LocalOfferSent) String() string {
3✔
742
        return fmt.Sprintf("LocalOfferSent(proposed_fee=%v)", l.ProposedFee)
3✔
743
}
3✔
744

745
// ShouldRouteTo returns true if the target state should process the target
746
// event.
747
func (l *LocalOfferSent) ShouldRouteTo(event ProtocolEvent) bool {
3✔
748
        switch event.(type) {
3✔
749
        case *LocalSigReceived:
3✔
750
                return true
3✔
751
        default:
3✔
752
                return false
3✔
753
        }
754
}
755

756
// protocolStateaSealed indicates that this struct is a ProtocolEvent instance.
757
func (l *LocalOfferSent) protocolStateSealed() {}
×
758

759
// IsTerminal returns true if the target state is a terminal state.
760
func (l *LocalOfferSent) IsTerminal() bool {
×
761
        return false
×
762
}
×
763

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

780
        *CloseChannelTerms
781

782
        // FeeRate is the fee rate of the closing transaction.
783
        FeeRate chainfee.SatPerVByte
784

785
        // Party indicates which party is at this state. This is used to
786
        // implement the state transition properly, based on ShouldRouteTo.
787
        Party lntypes.ChannelParty
788
}
789

790
// String returns the name of the state for ClosePending.
791
func (c *ClosePending) String() string {
3✔
792
        return fmt.Sprintf("ClosePending(txid=%v, party=%v, fee_rate=%v)",
3✔
793
                c.CloseTx.TxHash(), c.Party, c.FeeRate)
3✔
794
}
3✔
795

796
// isType returns true if the value is of type T.
797
func isType[T any](value any) bool {
3✔
798
        _, ok := value.(T)
3✔
799
        return ok
3✔
800
}
3✔
801

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

813
                case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
814
                        event,
815
                ):
3✔
816

3✔
817
                        return true
3✔
818
                }
819

820
                return false
3✔
821
        }
822
}
823

824
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
825
func (c *ClosePending) protocolStateSealed() {}
×
826

827
// IsTerminal returns true if the target state is a terminal state.
828
func (c *ClosePending) IsTerminal() bool {
×
829
        return true
×
830
}
×
831

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

839
// String returns the name of the state for CloseFin.
840
func (c *CloseFin) String() string {
3✔
841
        return "CloseFin"
3✔
842
}
3✔
843

844
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
845
func (c *CloseFin) protocolStateSealed() {}
×
846

847
// IsTerminal returns true if the target state is a terminal state.
848
func (c *CloseFin) IsTerminal() bool {
×
849
        return true
×
850
}
×
851

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

862
// String returns the name of the state for RemoteCloseStart.
863
func (r *RemoteCloseStart) String() string {
3✔
864
        return "RemoteCloseStart"
3✔
865
}
3✔
866

867
// ShouldRouteTo returns true if the target state should process the target
868
// event.
869
func (l *RemoteCloseStart) ShouldRouteTo(event ProtocolEvent) bool {
3✔
870
        switch event.(type) {
3✔
871
        case *OfferReceivedEvent:
3✔
872
                return true
3✔
UNCOV
873
        default:
×
UNCOV
874
                return false
×
875
        }
876
}
877

878
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
879
func (l *RemoteCloseStart) protocolStateSealed() {}
×
880

881
// IsTerminal returns true if the target state is a terminal state.
882
func (l *RemoteCloseStart) IsTerminal() bool {
×
883
        return false
×
884
}
×
885

886
// CloseErr is an error state in the protocol. We enter this state when a
887
// protocol constraint is violated, or an upfront sanity check fails.
888
type CloseErr struct {
889
        ErrState
890

891
        *CloseChannelTerms
892

893
        // Party indicates which party is at this state. This is used to
894
        // implement the state transition properly, based on ShouldRouteTo.
895
        Party lntypes.ChannelParty
896
}
897

898
// String returns the name of the state for CloseErr, including error and party
899
// details.
900
func (c *CloseErr) String() string {
×
901
        return fmt.Sprintf("CloseErr(party=%v, err=%v)", c.Party, c.ErrState)
×
902
}
×
903

904
// ShouldRouteTo returns true if the target state should process the target
905
// event.
UNCOV
906
func (c *CloseErr) ShouldRouteTo(event ProtocolEvent) bool {
×
UNCOV
907
        switch event.(type) {
×
908
        case *SpendEvent:
×
909
                return true
×
UNCOV
910
        default:
×
UNCOV
911
                switch {
×
UNCOV
912
                case c.Party == lntypes.Local && isType[*SendOfferEvent](event):
×
UNCOV
913
                        return true
×
914

915
                case c.Party == lntypes.Remote && isType[*OfferReceivedEvent](
916
                        event,
UNCOV
917
                ):
×
UNCOV
918

×
UNCOV
919
                        return true
×
920
                }
921

922
                return false
×
923
        }
924
}
925

926
// protocolStateSealed indicates that this struct is a ProtocolEvent instance.
927
func (c *CloseErr) protocolStateSealed() {}
×
928

929
// IsTerminal returns true if the target state is a terminal state.
930
func (c *CloseErr) IsTerminal() bool {
×
931
        return true
×
932
}
×
933

934
// RbfChanCloser is a state machine that handles the RBF-enabled cooperative
935
// channel close protocol.
936
type RbfChanCloser = protofsm.StateMachine[ProtocolEvent, *Environment]
937

938
// RbfChanCloserCfg is a configuration struct that is used to initialize a new
939
// RBF chan closer state machine.
940
type RbfChanCloserCfg = protofsm.StateMachineCfg[ProtocolEvent, *Environment]
941

942
// RbfSpendMapper is a type used to map the generic spend event to one specific
943
// to this package.
944
type RbfSpendMapper = protofsm.SpendMapper[ProtocolEvent]
945

946
func SpendMapper(spendEvent *chainntnfs.SpendDetail) ProtocolEvent {
3✔
947
        return &SpendEvent{
3✔
948
                Tx:          spendEvent.SpendingTx,
3✔
949
                BlockHeight: uint32(spendEvent.SpendingHeight),
3✔
950
        }
3✔
951
}
3✔
952

953
// RbfMsgMapperT is a type used to map incoming wire messages to protocol
954
// events.
955
type RbfMsgMapperT = protofsm.MsgMapper[ProtocolEvent]
956

957
// RbfState is a type alias for the state of the RBF channel closer.
958
type RbfState = protofsm.State[ProtocolEvent, *Environment]
959

960
// RbfEvent is a type alias for the event type of the RBF channel closer.
961
type RbfEvent = protofsm.EmittedEvent[ProtocolEvent]
962

963
// RbfStateSub is a type alias for the state subscription type of the RBF chan
964
// closer.
965
type RbfStateSub = protofsm.StateSubscriber[ProtocolEvent, *Environment]
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