• 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

77.46
/lnwallet/chancloser/rbf_coop_transitions.go
1
package chancloser
2

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

7
        "github.com/btcsuite/btcd/btcec/v2"
8
        "github.com/btcsuite/btcd/btcutil"
9
        "github.com/btcsuite/btcd/chaincfg"
10
        "github.com/btcsuite/btcd/mempool"
11
        "github.com/btcsuite/btcd/wire"
12
        "github.com/davecgh/go-spew/spew"
13
        "github.com/lightningnetwork/lnd/fn/v2"
14
        "github.com/lightningnetwork/lnd/input"
15
        "github.com/lightningnetwork/lnd/labels"
16
        "github.com/lightningnetwork/lnd/lntypes"
17
        "github.com/lightningnetwork/lnd/lnutils"
18
        "github.com/lightningnetwork/lnd/lnwallet"
19
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
20
        "github.com/lightningnetwork/lnd/lnwire"
21
        "github.com/lightningnetwork/lnd/protofsm"
22
        "github.com/lightningnetwork/lnd/tlv"
23
)
24

25
// sendShutdownEvents is a helper function that returns a set of daemon events
26
// we need to emit when we decide that we should send a shutdown message. We'll
27
// also mark the channel as borked as well, as at this point, we no longer want
28
// to continue with normal operation.
29
func sendShutdownEvents(chanID lnwire.ChannelID, chanPoint wire.OutPoint,
30
        deliveryAddr lnwire.DeliveryAddress, peerPub btcec.PublicKey,
31
        postSendEvent fn.Option[ProtocolEvent],
32
        chanState ChanStateObserver) (protofsm.DaemonEventSet, error) {
2✔
33

2✔
34
        // We'll emit a daemon event that instructs the daemon to send out a
2✔
35
        // new shutdown message to the remote peer.
2✔
36
        msgsToSend := &protofsm.SendMsgEvent[ProtocolEvent]{
2✔
37
                TargetPeer: peerPub,
2✔
38
                Msgs: []lnwire.Message{&lnwire.Shutdown{
2✔
39
                        ChannelID: chanID,
2✔
40
                        Address:   deliveryAddr,
2✔
41
                }},
2✔
42
                SendWhen: fn.Some(func() bool {
5✔
43
                        ok := chanState.NoDanglingUpdates()
3✔
44
                        if ok {
4✔
45
                                chancloserLog.Infof("ChannelPoint(%v): no "+
1✔
46
                                        "dangling updates sending shutdown "+
1✔
47
                                        "message", chanPoint)
1✔
48
                        }
1✔
49

50
                        return ok
3✔
51
                }),
52
                PostSendEvent: postSendEvent,
53
        }
54

55
        // If a close is already in process (we're in the RBF loop), then we
56
        // can skip everything below, and just send out the shutdown message.
57
        if chanState.FinalBalances().IsSome() {
2✔
UNCOV
58
                return protofsm.DaemonEventSet{msgsToSend}, nil
×
UNCOV
59
        }
×
60

61
        // Before closing, we'll attempt to send a disable update for the
62
        // channel.  We do so before closing the channel as otherwise the
63
        // current edge policy won't be retrievable from the graph.
64
        if err := chanState.DisableChannel(); err != nil {
2✔
65
                return nil, fmt.Errorf("unable to disable channel: %w", err)
×
66
        }
×
67

68
        // If we have a post-send event, then this means that we're the
69
        // responder. We'll use this fact below to update state in the DB.
70
        isInitiator := postSendEvent.IsNone()
2✔
71

2✔
72
        chancloserLog.Infof("ChannelPoint(%v): disabling outgoing adds",
2✔
73
                chanPoint)
2✔
74

2✔
75
        // As we're about to send a shutdown, we'll disable adds in the
2✔
76
        // outgoing direction.
2✔
77
        if err := chanState.DisableOutgoingAdds(); err != nil {
2✔
78
                return nil, fmt.Errorf("unable to disable outgoing "+
×
79
                        "adds: %w", err)
×
80
        }
×
81

82
        // To be able to survive a restart, we'll also write to disk
83
        // information about the shutdown we're about to send out.
84
        err := chanState.MarkShutdownSent(deliveryAddr, isInitiator)
2✔
85
        if err != nil {
2✔
86
                return nil, fmt.Errorf("unable to mark shutdown sent: %w", err)
×
87
        }
×
88

89
        chancloserLog.Debugf("ChannelPoint(%v): marking channel as borked",
2✔
90
                chanPoint)
2✔
91

2✔
92
        return protofsm.DaemonEventSet{msgsToSend}, nil
2✔
93
}
94

95
// validateShutdown is a helper function that validates that the shutdown has a
96
// proper delivery script, and can be sent based on the current thaw height of
97
// the channel.
98
func validateShutdown(chanThawHeight fn.Option[uint32],
99
        upfrontAddr fn.Option[lnwire.DeliveryAddress],
100
        msg *ShutdownReceived, chanPoint wire.OutPoint,
101
        chainParams chaincfg.Params) error {
3✔
102

3✔
103
        // If we've received a shutdown message, and we have a thaw height,
3✔
104
        // then we need to make sure that the channel can now be co-op closed.
3✔
105
        err := fn.MapOptionZ(chanThawHeight, func(thawHeight uint32) error {
3✔
106
                // If the current height is below the thaw height, then we'll
×
107
                // reject the shutdown message as we can't yet co-op close the
×
108
                // channel.
×
109
                if msg.BlockHeight < thawHeight {
×
110
                        return fmt.Errorf("initiator attempting to "+
×
111
                                "co-op close frozen ChannelPoint(%v) "+
×
112
                                "(current_height=%v, thaw_height=%v)",
×
113
                                chanPoint, msg.BlockHeight,
×
114
                                thawHeight)
×
115
                }
×
116

117
                return nil
×
118
        })
119
        if err != nil {
3✔
120
                return err
×
121
        }
×
122

123
        // Next, we'll verify that the remote party is sending the expected
124
        // shutdown script.
125
        return fn.MapOption(func(addr lnwire.DeliveryAddress) error {
5✔
126
                return validateShutdownScript(
2✔
127
                        addr, msg.ShutdownScript, &chainParams,
2✔
128
                )
2✔
129
        })(upfrontAddr).UnwrapOr(nil)
2✔
130
}
131

132
// ProcessEvent takes a protocol event, and implements a state transition for
133
// the state. From this state, we can receive two possible incoming events:
134
// SendShutdown and ShutdownReceived. Both of these will transition us to the
135
// ChannelFlushing state.
136
func (c *ChannelActive) ProcessEvent(event ProtocolEvent, env *Environment,
137
) (*CloseStateTransition, error) {
5✔
138

5✔
139
        switch msg := event.(type) {
5✔
140
        // If we get a confirmation, then a prior transaction we broadcasted
141
        // has confirmed, so we can move to our terminal state early.
142
        case *SpendEvent:
1✔
143
                return &CloseStateTransition{
1✔
144
                        NextState: &CloseFin{
1✔
145
                                ConfirmedTx: msg.Tx,
1✔
146
                        },
1✔
147
                }, nil
1✔
148

149
        // If we receive the SendShutdown event, then we'll send our shutdown
150
        // with a special SendPredicate, then go to the ShutdownPending where
151
        // we'll wait for the remote to send their shutdown.
152
        case *SendShutdown:
2✔
153
                // If we have an upfront shutdown addr or a delivery addr then
2✔
154
                // we'll use that. Otherwise, we'll generate a new delivery
2✔
155
                // addr.
2✔
156
                shutdownScript, err := env.LocalUpfrontShutdown.Alt(
2✔
157
                        msg.DeliveryAddr,
2✔
158
                ).UnwrapOrFuncErr(env.NewDeliveryScript)
2✔
159
                if err != nil {
3✔
160
                        return nil, err
1✔
161
                }
1✔
162

163
                // We'll emit some daemon events to send the shutdown message
164
                // and disable the channel on the network level. In this case,
165
                // we don't need a post send event as receive their shutdown is
166
                // what'll move us beyond the ShutdownPending state.
167
                daemonEvents, err := sendShutdownEvents(
1✔
168
                        env.ChanID, env.ChanPoint, shutdownScript,
1✔
169
                        env.ChanPeer, fn.None[ProtocolEvent](),
1✔
170
                        env.ChanObserver,
1✔
171
                )
1✔
172
                if err != nil {
1✔
173
                        return nil, err
×
174
                }
×
175

176
                chancloserLog.Infof("ChannelPoint(%v): sending shutdown msg, "+
1✔
177
                        "delivery_script=%x", env.ChanPoint, shutdownScript)
1✔
178

1✔
179
                // From here, we'll transition to the shutdown pending state. In
1✔
180
                // this state we await their shutdown message (self loop), then
1✔
181
                // also the flushing event.
1✔
182
                return &CloseStateTransition{
1✔
183
                        NextState: &ShutdownPending{
1✔
184
                                IdealFeeRate: fn.Some(msg.IdealFeeRate),
1✔
185
                                ShutdownScripts: ShutdownScripts{
1✔
186
                                        LocalDeliveryScript: shutdownScript,
1✔
187
                                },
1✔
188
                        },
1✔
189
                        NewEvents: fn.Some(RbfEvent{
1✔
190
                                ExternalEvents: daemonEvents,
1✔
191
                        }),
1✔
192
                }, nil
1✔
193

194
        // When we receive a shutdown from the remote party, we'll validate the
195
        // shutdown message, then transition to the ShutdownPending state. We'll
196
        // also emit similar events like the above to send out shutdown, and
197
        // also disable the channel.
198
        case *ShutdownReceived:
1✔
199
                chancloserLog.Infof("ChannelPoint(%v): received shutdown msg")
1✔
200

1✔
201
                // Validate that they can send the message now, and also that
1✔
202
                // they haven't violated their commitment to a prior upfront
1✔
203
                // shutdown addr.
1✔
204
                err := validateShutdown(
1✔
205
                        env.ThawHeight, env.RemoteUpfrontShutdown, msg,
1✔
206
                        env.ChanPoint, env.ChainParams,
1✔
207
                )
1✔
208
                if err != nil {
1✔
209
                        chancloserLog.Errorf("ChannelPoint(%v): rejecting "+
×
210
                                "shutdown attempt: %v", err)
×
211

×
212
                        return nil, err
×
213
                }
×
214

215
                // If we have an upfront shutdown addr we'll use that,
216
                // otherwise, we'll generate a new delivery script.
217
                shutdownAddr, err := env.LocalUpfrontShutdown.UnwrapOrFuncErr(
1✔
218
                        env.NewDeliveryScript,
1✔
219
                )
1✔
220
                if err != nil {
1✔
221
                        return nil, err
×
222
                }
×
223

224
                chancloserLog.Infof("ChannelPoint(%v): sending shutdown msg "+
1✔
225
                        "at next clean commit state", env.ChanPoint)
1✔
226

1✔
227
                // Now that we know the shutdown message is valid, we'll obtain
1✔
228
                // the set of daemon events we need to emit. We'll also specify
1✔
229
                // that once the message has actually been sent, that we
1✔
230
                // generate receive an input event of a ShutdownComplete.
1✔
231
                daemonEvents, err := sendShutdownEvents(
1✔
232
                        env.ChanID, env.ChanPoint, shutdownAddr,
1✔
233
                        env.ChanPeer,
1✔
234
                        fn.Some[ProtocolEvent](&ShutdownComplete{}),
1✔
235
                        env.ChanObserver,
1✔
236
                )
1✔
237
                if err != nil {
1✔
238
                        return nil, err
×
239
                }
×
240

241
                chancloserLog.Infof("ChannelPoint(%v): disabling incoming adds",
1✔
242
                        env.ChanPoint)
1✔
243

1✔
244
                // We just received a shutdown, so we'll disable the adds in
1✔
245
                // the outgoing direction.
1✔
246
                if err := env.ChanObserver.DisableIncomingAdds(); err != nil {
1✔
247
                        return nil, fmt.Errorf("unable to disable incoming "+
×
248
                                "adds: %w", err)
×
249
                }
×
250

251
                remoteAddr := msg.ShutdownScript
1✔
252

1✔
253
                return &CloseStateTransition{
1✔
254
                        NextState: &ShutdownPending{
1✔
255
                                ShutdownScripts: ShutdownScripts{
1✔
256
                                        LocalDeliveryScript:  shutdownAddr,
1✔
257
                                        RemoteDeliveryScript: remoteAddr,
1✔
258
                                },
1✔
259
                        },
1✔
260
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
1✔
261
                                ExternalEvents: daemonEvents,
1✔
262
                        }),
1✔
263
                }, nil
1✔
264

265
        // Any other messages in this state will result in an error, as this is
266
        // an undefined state transition.
267
        default:
1✔
268
                return nil, fmt.Errorf("%w: received %T while in ChannelActive",
1✔
269
                        ErrInvalidStateTransition, msg)
1✔
270
        }
271
}
272

273
// ProcessEvent takes a protocol event, and implements a state transition for
274
// the state. Our path to this state will determine the set of valid events. If
275
// we were the one that sent the shutdown, then we'll just wait on the
276
// ShutdownReceived event. Otherwise, we received the shutdown, and can move
277
// forward once we receive the ShutdownComplete event. Receiving
278
// ShutdownComplete means that we've sent our shutdown, as this was specified
279
// as a post send event.
280
func (s *ShutdownPending) ProcessEvent(event ProtocolEvent, env *Environment,
281
) (*CloseStateTransition, error) {
5✔
282

5✔
283
        switch msg := event.(type) {
5✔
284
        // If we get a confirmation, then a prior transaction we broadcasted
285
        // has confirmed, so we can move to our terminal state early.
286
        case *SpendEvent:
1✔
287
                return &CloseStateTransition{
1✔
288
                        NextState: &CloseFin{
1✔
289
                                ConfirmedTx: msg.Tx,
1✔
290
                        },
1✔
291
                }, nil
1✔
292

293
        // When we receive a shutdown from the remote party, we'll validate the
294
        // shutdown message, then transition to the ChannelFlushing state.
295
        case *ShutdownReceived:
2✔
296
                chancloserLog.Infof("ChannelPoint(%v): received shutdown msg",
2✔
297
                        env.ChanPoint)
2✔
298

2✔
299
                // Validate that they can send the message now, and also that
2✔
300
                // they haven't violated their commitment to a prior upfront
2✔
301
                // shutdown addr.
2✔
302
                err := validateShutdown(
2✔
303
                        env.ThawHeight, env.RemoteUpfrontShutdown, msg,
2✔
304
                        env.ChanPoint, env.ChainParams,
2✔
305
                )
2✔
306
                if err != nil {
3✔
307
                        chancloserLog.Errorf("ChannelPoint(%v): rejecting "+
1✔
308
                                "shutdown attempt: %v", err)
1✔
309

1✔
310
                        return nil, err
1✔
311
                }
1✔
312

313
                // If the channel is *already* flushed, and the close is
314
                // go straight into negotiation, as this is the RBF loop.
315
                // already in progress, then we can skip the flushing state and
316
                var eventsToEmit fn.Option[protofsm.EmittedEvent[ProtocolEvent]]
1✔
317
                finalBalances := env.ChanObserver.FinalBalances().UnwrapOr(
1✔
318
                        unknownBalance,
1✔
319
                )
1✔
320
                if finalBalances != unknownBalance {
1✔
UNCOV
321
                        channelFlushed := ProtocolEvent(&ChannelFlushed{
×
UNCOV
322
                                ShutdownBalances: finalBalances,
×
UNCOV
323
                        })
×
UNCOV
324
                        eventsToEmit = fn.Some(RbfEvent{
×
UNCOV
325
                                InternalEvent: []ProtocolEvent{
×
UNCOV
326
                                        channelFlushed,
×
UNCOV
327
                                },
×
UNCOV
328
                        })
×
UNCOV
329
                }
×
330

331
                chancloserLog.Infof("ChannelPoint(%v): disabling incoming adds",
1✔
332
                        env.ChanPoint)
1✔
333

1✔
334
                // We just received a shutdown, so we'll disable the adds in
1✔
335
                // the outgoing direction.
1✔
336
                if err := env.ChanObserver.DisableIncomingAdds(); err != nil {
1✔
337
                        return nil, fmt.Errorf("unable to disable incoming "+
×
338
                                "adds: %w", err)
×
339
                }
×
340

341
                chancloserLog.Infof("ChannelPoint(%v): waiting for channel to "+
1✔
342
                        "be flushed...", env.ChanPoint)
1✔
343

1✔
344
                // We transition to the ChannelFlushing state, where we await
1✔
345
                // the ChannelFlushed event.
1✔
346
                return &CloseStateTransition{
1✔
347
                        NextState: &ChannelFlushing{
1✔
348
                                IdealFeeRate: s.IdealFeeRate,
1✔
349
                                ShutdownScripts: ShutdownScripts{
1✔
350
                                        LocalDeliveryScript:  s.LocalDeliveryScript, //nolint:ll
1✔
351
                                        RemoteDeliveryScript: msg.ShutdownScript,    //nolint:ll
1✔
352
                                },
1✔
353
                        },
1✔
354
                        NewEvents: eventsToEmit,
1✔
355
                }, nil
1✔
356

357
        // If we get this message, then this means that we were finally able to
358
        // send out shutdown after receiving it from the remote party. We'll
359
        // now transition directly to the ChannelFlushing state.
360
        case *ShutdownComplete:
1✔
361
                chancloserLog.Infof("ChannelPoint(%v): waiting for channel to "+
1✔
362
                        "be flushed...", env.ChanPoint)
1✔
363

1✔
364
                // If the channel is *already* flushed, and the close is
1✔
365
                // already in progress, then we can skip the flushing state and
1✔
366
                // go straight into negotiation, as this is the RBF loop.
1✔
367
                var eventsToEmit fn.Option[protofsm.EmittedEvent[ProtocolEvent]]
1✔
368
                finalBalances := env.ChanObserver.FinalBalances().UnwrapOr(
1✔
369
                        unknownBalance,
1✔
370
                )
1✔
371
                if finalBalances != unknownBalance {
1✔
UNCOV
372
                        channelFlushed := ProtocolEvent(&ChannelFlushed{
×
UNCOV
373
                                ShutdownBalances: finalBalances,
×
UNCOV
374
                        })
×
UNCOV
375
                        eventsToEmit = fn.Some(RbfEvent{
×
UNCOV
376
                                InternalEvent: []ProtocolEvent{
×
UNCOV
377
                                        channelFlushed,
×
UNCOV
378
                                },
×
UNCOV
379
                        })
×
UNCOV
380
                }
×
381

382
                // From here, we'll transition to the channel flushing state.
383
                // We'll stay here until we receive the ChannelFlushed event.
384
                return &CloseStateTransition{
1✔
385
                        NextState: &ChannelFlushing{
1✔
386
                                IdealFeeRate:    s.IdealFeeRate,
1✔
387
                                ShutdownScripts: s.ShutdownScripts,
1✔
388
                        },
1✔
389
                        NewEvents: eventsToEmit,
1✔
390
                }, nil
1✔
391

392
        // Any other messages in this state will result in an error, as this is
393
        // an undefined state transition.
394
        default:
1✔
395
                return nil, fmt.Errorf("%w: received %T while in "+
1✔
396
                        "ShutdownPending", ErrInvalidStateTransition, msg)
1✔
397
        }
398
}
399

400
// ProcessEvent takes a new protocol event, and figures out if we can
401
// transition to the next state, or just loop back upon ourself. If we receive
402
// a ShutdownReceived event, then we'll stay in the ChannelFlushing state, as
403
// we haven't yet fully cleared the channel. Otherwise, we can move to the
404
// CloseReady state which'll being the channel closing process.
405
func (c *ChannelFlushing) ProcessEvent(event ProtocolEvent, env *Environment,
406
) (*CloseStateTransition, error) {
5✔
407

5✔
408
        switch msg := event.(type) {
5✔
409
        // If we get a confirmation, then a prior transaction we broadcasted
410
        // has confirmed, so we can move to our terminal state early.
411
        case *SpendEvent:
×
412
                return &CloseStateTransition{
×
413
                        NextState: &CloseFin{
×
414
                                ConfirmedTx: msg.Tx,
×
415
                        },
×
416
                }, nil
×
417

418
        // If we get an OfferReceived event, then the channel is flushed from
419
        // the PoV of the remote party. However, due to propagation delay or
420
        // concurrency, we may not have received the ChannelFlushed event yet.
421
        // In this case, we'll stash the event and wait for the ChannelFlushed
422
        // event.
423
        case *OfferReceivedEvent:
×
424
                chancloserLog.Infof("ChannelPoint(%v): received remote offer "+
×
425
                        "early, stashing...", env.ChanPoint)
×
426

×
427
                c.EarlyRemoteOffer = fn.Some(*msg)
×
428

×
429
                // We'll perform a noop update so we can wait for the actual
×
430
                // channel flushed event.
×
431
                return &CloseStateTransition{
×
432
                        NextState: c,
×
433
                }, nil
×
434

435
        // If we receive the ChannelFlushed event, then the coast is clear so
436
        // we'll now morph into the dual peer state so we can handle any
437
        // messages needed to drive forward the close process.
438
        case *ChannelFlushed:
4✔
439
                // Both the local and remote losing negotiation needs the terms
4✔
440
                // we'll be using to close the channel, so we'll create them
4✔
441
                // here.
4✔
442
                closeTerms := CloseChannelTerms{
4✔
443
                        ShutdownScripts:  c.ShutdownScripts,
4✔
444
                        ShutdownBalances: msg.ShutdownBalances,
4✔
445
                }
4✔
446

4✔
447
                chancloserLog.Infof("ChannelPoint(%v): channel flushed! "+
4✔
448
                        "proceeding with co-op close", env.ChanPoint)
4✔
449

4✔
450
                // Now that the channel has been flushed, we'll mark on disk
4✔
451
                // that we're approaching the point of no return where we'll
4✔
452
                // send a new signature to the remote party.
4✔
453
                //
4✔
454
                // TODO(roasbeef): doesn't actually matter if initiator here?
4✔
455
                if msg.FreshFlush {
6✔
456
                        err := env.ChanObserver.MarkCoopBroadcasted(nil, true)
2✔
457
                        if err != nil {
2✔
458
                                return nil, err
×
459
                        }
×
460
                }
461

462
                // If an ideal fee rate was specified, then we'll use that,
463
                // otherwise we'll fall back to the default value given in the
464
                // env.
465
                idealFeeRate := c.IdealFeeRate.UnwrapOr(env.DefaultFeeRate)
4✔
466

4✔
467
                // We'll then use that fee rate to determine the absolute fee
4✔
468
                // we'd propose.
4✔
469
                localTxOut, remoteTxOut := closeTerms.DeriveCloseTxOuts()
4✔
470
                absoluteFee := env.FeeEstimator.EstimateFee(
4✔
471
                        env.ChanType, localTxOut, remoteTxOut,
4✔
472
                        idealFeeRate.FeePerKWeight(),
4✔
473
                )
4✔
474

4✔
475
                chancloserLog.Infof("ChannelPoint(%v): using ideal_fee=%v, "+
4✔
476
                        "absolute_fee=%v", env.ChanPoint, idealFeeRate,
4✔
477
                        absoluteFee)
4✔
478

4✔
479
                var (
4✔
480
                        internalEvents []ProtocolEvent
4✔
481
                        newEvents      fn.Option[RbfEvent]
4✔
482
                )
4✔
483

4✔
484
                // If we received a remote offer early from the remote party,
4✔
485
                // then we'll add that to the set of internal events to emit.
4✔
486
                c.EarlyRemoteOffer.WhenSome(func(offer OfferReceivedEvent) {
4✔
487
                        internalEvents = append(internalEvents, &offer)
×
488
                })
×
489

490
                // Only if we have enough funds to pay for the fees do we need
491
                // to emit a localOfferSign event.
492
                //
493
                // TODO(roasbeef): also only proceed if was higher than fee in
494
                // last round?
495
                if closeTerms.LocalCanPayFees(absoluteFee) {
6✔
496
                        // Each time we go into this negotiation flow, we'll
2✔
497
                        // kick off our local state with a new close attempt.
2✔
498
                        // So we'll emit a internal event to drive forward that
2✔
499
                        // part of the state.
2✔
500
                        localOfferSign := ProtocolEvent(&SendOfferEvent{
2✔
501
                                TargetFeeRate: idealFeeRate,
2✔
502
                        })
2✔
503
                        internalEvents = append(internalEvents, localOfferSign)
2✔
504
                } else {
4✔
505
                        chancloserLog.Infof("ChannelPoint(%v): unable to pay "+
2✔
506
                                "fees with local balance, skipping "+
2✔
507
                                "closing_complete", env.ChanPoint)
2✔
508
                }
2✔
509

510
                if len(internalEvents) > 0 {
6✔
511
                        newEvents = fn.Some(RbfEvent{
2✔
512
                                InternalEvent: internalEvents,
2✔
513
                        })
2✔
514
                }
2✔
515

516
                return &CloseStateTransition{
4✔
517
                        NextState: &ClosingNegotiation{
4✔
518
                                PeerState: lntypes.Dual[AsymmetricPeerState]{
4✔
519
                                        Local: &LocalCloseStart{
4✔
520
                                                CloseChannelTerms: &closeTerms,
4✔
521
                                        },
4✔
522
                                        Remote: &RemoteCloseStart{
4✔
523
                                                CloseChannelTerms: &closeTerms,
4✔
524
                                        },
4✔
525
                                },
4✔
526
                                CloseChannelTerms: &closeTerms,
4✔
527
                        },
4✔
528
                        NewEvents: newEvents,
4✔
529
                }, nil
4✔
530

531
        default:
1✔
532
                return nil, fmt.Errorf("%w: received %T while in "+
1✔
533
                        "ChannelFlushing", ErrInvalidStateTransition, msg)
1✔
534
        }
535
}
536

537
// processNegotiateEvent is a helper function that processes a new event to
538
// local channel state once we're in the ClosingNegotiation state.
539
func processNegotiateEvent(c *ClosingNegotiation, event ProtocolEvent,
540
        env *Environment, chanPeer lntypes.ChannelParty,
541
) (*CloseStateTransition, error) {
20✔
542

20✔
543
        targetPeerState := c.PeerState.GetForParty(chanPeer)
20✔
544

20✔
545
        // Drive forward the remote state based on the next event.
20✔
546
        transition, err := targetPeerState.ProcessEvent(
20✔
547
                event, env,
20✔
548
        )
20✔
549
        if err != nil {
24✔
550
                return nil, err
4✔
551
        }
4✔
552

553
        nextPeerState, ok := transition.NextState.(AsymmetricPeerState) //nolint:ll
16✔
554
        if !ok {
16✔
555
                return nil, fmt.Errorf("expected %T to be "+
×
556
                        "AsymmetricPeerState", transition.NextState)
×
557
        }
×
558

559
        // Make a copy of the input state, then update the peer state of the
560
        // proper party.
561
        newPeerState := *c
16✔
562
        newPeerState.PeerState.SetForParty(chanPeer, nextPeerState)
16✔
563

16✔
564
        return &CloseStateTransition{
16✔
565
                NextState: &newPeerState,
16✔
566
                NewEvents: transition.NewEvents,
16✔
567
        }, nil
16✔
568
}
569

570
// updateAndValidateCloseTerms is a helper function that validates examines the
571
// incoming event, and decide if we need to update the remote party's address,
572
// or reject it if it doesn't include our latest address.
573
func (c *ClosingNegotiation) updateAndValidateCloseTerms(event ProtocolEvent,
574
) error {
22✔
575

22✔
576
        assertLocalScriptMatches := func(localScriptInMsg []byte) error {
35✔
577
                if !bytes.Equal(
13✔
578
                        c.LocalDeliveryScript, localScriptInMsg,
13✔
579
                ) {
15✔
580

2✔
581
                        return fmt.Errorf("%w: remote party sent wrong "+
2✔
582
                                "script, expected %x, got %x",
2✔
583
                                ErrWrongLocalScript, c.LocalDeliveryScript,
2✔
584
                                localScriptInMsg,
2✔
585
                        )
2✔
586
                }
2✔
587

588
                return nil
11✔
589
        }
590

591
        switch msg := event.(type) {
22✔
592
        // The remote party is sending us a new request to counter sign their
593
        // version of the commitment transaction.
594
        case *OfferReceivedEvent:
8✔
595
                // Make sure that they're sending our local script, and not
8✔
596
                // something else.
8✔
597
                err := assertLocalScriptMatches(msg.SigMsg.CloseeScript)
8✔
598
                if err != nil {
9✔
599
                        return err
1✔
600
                }
1✔
601

602
                oldRemoteAddr := c.RemoteDeliveryScript
7✔
603
                newRemoteAddr := msg.SigMsg.CloserScript
7✔
604

7✔
605
                // If they're sending a new script, then we'll update to the new
7✔
606
                // one.
7✔
607
                if !bytes.Equal(oldRemoteAddr, newRemoteAddr) {
8✔
608
                        c.RemoteDeliveryScript = newRemoteAddr
1✔
609
                }
1✔
610

611
        // The remote party responded to our sig request with a signature for
612
        // our version of the commitment transaction.
613
        case *LocalSigReceived:
5✔
614
                // Make sure that they're sending our local script, and not
5✔
615
                // something else.
5✔
616
                err := assertLocalScriptMatches(msg.SigMsg.CloserScript)
5✔
617
                if err != nil {
6✔
618
                        return err
1✔
619
                }
1✔
620

621
                return nil
4✔
622
        }
623

624
        return nil
16✔
625
}
626

627
// ProcessEvent drives forward the composite states for the local and remote
628
// party in response to new events. From this state, we'll continue to drive
629
// forward the local and remote states until we arrive at the StateFin stage,
630
// or we loop back up to the ShutdownPending state.
631
func (c *ClosingNegotiation) ProcessEvent(event ProtocolEvent, env *Environment,
632
) (*CloseStateTransition, error) {
22✔
633

22✔
634
        // There're two classes of events that can break us out of this state:
22✔
635
        // we receive a confirmation event, or we receive a signal to restart
22✔
636
        // the co-op close process.
22✔
637
        switch msg := event.(type) {
22✔
638
        // Ignore any potential duplicate channel flushed events.
NEW
639
        case *ChannelFlushed:
×
NEW
640
                return &CloseStateTransition{
×
NEW
641
                        NextState: c,
×
NEW
642
                }, nil
×
643

644
        // If we get a confirmation, then the spend request we issued when we
645
        // were leaving the ChannelFlushing state has been confirmed.  We'll
646
        // now transition to the StateFin state.
647
        case *SpendEvent:
×
648
                return &CloseStateTransition{
×
649
                        NextState: &CloseFin{
×
650
                                ConfirmedTx: msg.Tx,
×
651
                        },
×
652
                }, nil
×
653
        }
654

655
        // At this point, we know its a new signature message. We'll validate,
656
        // and maybe update the set of close terms based on what we receive. We
657
        // might update the remote party's address for example.
658
        if err := c.updateAndValidateCloseTerms(event); err != nil {
24✔
659
                return nil, fmt.Errorf("event violates close terms: %w", err)
2✔
660
        }
2✔
661

662
        // If we get to this point, then we have an event that'll drive forward
663
        // the negotiation process.  Based on the event, we'll figure out which
664
        // state we'll be modifying.
665
        switch {
20✔
666
        case c.PeerState.GetForParty(lntypes.Local).ShouldRouteTo(event):
13✔
667
                chancloserLog.Infof("ChannelPoint(%v): routing %T to local "+
13✔
668
                        "chan state", env.ChanPoint, event)
13✔
669

13✔
670
                // Drive forward the local state based on the next event.
13✔
671
                return processNegotiateEvent(c, event, env, lntypes.Local)
13✔
672

673
        case c.PeerState.GetForParty(lntypes.Remote).ShouldRouteTo(event):
7✔
674
                chancloserLog.Infof("ChannelPoint(%v): routing %T to remote "+
7✔
675

7✔
676
                        "chan state", env.ChanPoint, event)
7✔
677
                // Drive forward the remote state based on the next event.
7✔
678
                return processNegotiateEvent(c, event, env, lntypes.Remote)
7✔
679
        }
680

NEW
681
        return nil, fmt.Errorf("%w: received %T while in %v",
×
NEW
682
                ErrInvalidStateTransition, event, c)
×
683
}
684

685
// newSigTlv is a helper function that returns a new optional TLV sig field for
686
// the parametrized tlv.TlvType value.
687
func newSigTlv[T tlv.TlvType](s lnwire.Sig) tlv.OptionalRecordT[T, lnwire.Sig] {
22✔
688
        return tlv.SomeRecordT(tlv.NewRecordT[T](s))
22✔
689
}
22✔
690

691
// ProcessEvent implements the event processing to kick off the process of
692
// obtaining a new (possibly RBF'd) signature for our commitment transaction.
693
func (l *LocalCloseStart) ProcessEvent(event ProtocolEvent, env *Environment,
694
) (*CloseStateTransition, error) {
8✔
695

8✔
696
        switch msg := event.(type) { //nolint:gocritic
8✔
697
        // If we receive a SendOfferEvent, then we'll use the specified fee
698
        // rate to generate for the closing transaction with our ideal fee
699
        // rate.
700
        case *SendOfferEvent:
8✔
701
                // given the state of the local/remote outputs.
8✔
702
                // First, we'll figure out the absolute fee rate we should pay
8✔
703
                localTxOut, remoteTxOut := l.DeriveCloseTxOuts()
8✔
704
                absoluteFee := env.FeeEstimator.EstimateFee(
8✔
705
                        env.ChanType, localTxOut, remoteTxOut,
8✔
706
                        msg.TargetFeeRate.FeePerKWeight(),
8✔
707
                )
8✔
708

8✔
709
                // If we can't actually pay for fees here, then we'll just do a
8✔
710
                // noop back to the same state to await a new fee rate.
8✔
711
                if !l.LocalCanPayFees(absoluteFee) {
8✔
NEW
712
                        chancloserLog.Infof("ChannelPoint(%v): unable to pay "+
×
NEW
713
                                "fee=%v with local balance %v, skipping "+
×
NEW
714
                                "closing_complete", env.ChanPoint, absoluteFee,
×
NEW
715
                                l.LocalBalance)
×
NEW
716

×
NEW
717
                        return &CloseStateTransition{
×
NEW
718
                                NextState: &CloseErr{
×
NEW
719
                                        CloseChannelTerms: l.CloseChannelTerms,
×
NEW
720
                                        Party:             lntypes.Local,
×
NEW
721
                                        ErrState: NewErrStateCantPayForFee(
×
NEW
722
                                                l.LocalBalance.ToSatoshis(),
×
NEW
723
                                                absoluteFee,
×
NEW
724
                                        ),
×
NEW
725
                                },
×
NEW
726
                        }, nil
×
NEW
727
                }
×
728

729
                // Now that we know what fee we want to pay, we'll create a new
730
                // signature over our co-op close transaction. For our
731
                // proposals, we'll just always use the known RBF sequence
732
                // value.
733
                localScript := l.LocalDeliveryScript
8✔
734
                rawSig, closeTx, closeBalance, err := env.CloseSigner.CreateCloseProposal( //nolint:ll
8✔
735
                        absoluteFee, localScript, l.RemoteDeliveryScript,
8✔
736
                        lnwallet.WithCustomSequence(mempool.MaxRBFSequence),
8✔
737
                        lnwallet.WithCustomPayer(lntypes.Local),
8✔
738
                )
8✔
739
                if err != nil {
8✔
740
                        return nil, err
×
741
                }
×
742
                wireSig, err := lnwire.NewSigFromSignature(rawSig)
8✔
743
                if err != nil {
8✔
744
                        return nil, err
×
745
                }
×
746

747
                chancloserLog.Infof("closing w/ local_addr=%x, "+
8✔
748
                        "remote_addr=%x, fee=%v", localScript[:],
8✔
749
                        l.RemoteDeliveryScript[:], absoluteFee)
8✔
750

8✔
751
                chancloserLog.Infof("proposing closing_tx=%v",
8✔
752
                        spew.Sdump(closeTx))
8✔
753

8✔
754
                // Now that we have our signature, we'll set the proper
8✔
755
                // closingSigs field based on if the remote party's output is
8✔
756
                // dust or not.
8✔
757
                var closingSigs lnwire.ClosingSigs
8✔
758
                switch {
8✔
759
                // If the remote party's output is dust, then we'll set the
760
                // CloserNoClosee field.
761
                case remoteTxOut == nil:
1✔
762
                        closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1](
1✔
763
                                wireSig,
1✔
764
                        )
1✔
765

766
                // If after paying for fees, our balance is below dust, then
767
                // we'll set the NoCloserClosee field.
768
                case closeBalance < lnwallet.DustLimitForSize(len(localScript)):
1✔
769
                        closingSigs.NoCloserClosee = newSigTlv[tlv.TlvType2](
1✔
770
                                wireSig,
1✔
771
                        )
1✔
772

773
                // Otherwise, we'll set the CloserAndClosee field.
774
                //
775
                // TODO(roasbeef): should actually set both??
776
                default:
6✔
777
                        closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3](
6✔
778
                                wireSig,
6✔
779
                        )
6✔
780
                }
781

782
                // Now that we have our sig, we'll emit a daemon event to send
783
                // it to the remote party, then transition to the
784
                // LocalOfferSent state.
785
                //
786
                // TODO(roasbeef): type alias for protocol event
787
                sendEvent := protofsm.DaemonEventSet{&protofsm.SendMsgEvent[ProtocolEvent]{ //nolint:ll
8✔
788
                        TargetPeer: env.ChanPeer,
8✔
789
                        Msgs: []lnwire.Message{&lnwire.ClosingComplete{
8✔
790
                                ChannelID:    env.ChanID,
8✔
791
                                CloserScript: l.LocalDeliveryScript,
8✔
792
                                CloseeScript: l.RemoteDeliveryScript,
8✔
793
                                FeeSatoshis:  absoluteFee,
8✔
794
                                LockTime:     env.BlockHeight,
8✔
795
                                ClosingSigs:  closingSigs,
8✔
796
                        }},
8✔
797
                }}
8✔
798

8✔
799
                chancloserLog.Infof("ChannelPoint(%v): sending closing sig "+
8✔
800
                        "to remote party, fee_sats=%v", env.ChanPoint,
8✔
801
                        absoluteFee)
8✔
802

8✔
803
                return &CloseStateTransition{
8✔
804
                        NextState: &LocalOfferSent{
8✔
805
                                ProposedFee:       absoluteFee,
8✔
806
                                ProposedFeeRate:   msg.TargetFeeRate,
8✔
807
                                LocalSig:          wireSig,
8✔
808
                                CloseChannelTerms: l.CloseChannelTerms,
8✔
809
                        },
8✔
810
                        NewEvents: fn.Some(RbfEvent{
8✔
811
                                ExternalEvents: sendEvent,
8✔
812
                        }),
8✔
813
                }, nil
8✔
814
        }
815

816
        return nil, fmt.Errorf("%w: received %T while in LocalCloseStart",
×
817
                ErrInvalidStateTransition, event)
×
818
}
819

820
// extractSig extracts the expected signature from the closing sig message.
821
// Only one of them should actually be populated as the closing sig message is
822
// sent in response to a ClosingComplete message, it should only sign the same
823
// version of the co-op close tx as the sender did.
824
func extractSig(msg lnwire.ClosingSig) fn.Result[lnwire.Sig] {
4✔
825
        // First, we'll validate that only one signature is included in their
4✔
826
        // response to our initial offer. If not, then we'll exit here, and
4✔
827
        // trigger a recycle of the connection.
4✔
828
        sigInts := []bool{
4✔
829
                msg.CloserNoClosee.IsSome(), msg.NoCloserClosee.IsSome(),
4✔
830
                msg.CloserAndClosee.IsSome(),
4✔
831
        }
4✔
832
        numSigs := fn.Foldl(0, sigInts, func(acc int, sigInt bool) int {
16✔
833
                if sigInt {
17✔
834
                        return acc + 1
5✔
835
                }
5✔
836

837
                return acc
7✔
838
        })
839
        if numSigs != 1 {
5✔
840
                return fn.Errf[lnwire.Sig]("%w: only one sig should be set, "+
1✔
841
                        "got %v", ErrTooManySigs, numSigs)
1✔
842
        }
1✔
843

844
        // The final sig is the one that's actually set.
845
        sig := msg.CloserAndClosee.ValOpt().Alt(
3✔
846
                msg.NoCloserClosee.ValOpt(),
3✔
847
        ).Alt(
3✔
848
                msg.CloserNoClosee.ValOpt(),
3✔
849
        )
3✔
850

3✔
851
        return fn.NewResult(sig.UnwrapOrErr(ErrNoSig))
3✔
852
}
853

854
// ProcessEvent implements the state transition function for the
855
// LocalOfferSent state. In this state, we'll wait for the remote party to
856
// send a close_signed message which gives us the ability to broadcast a new
857
// co-op close transaction.
858
func (l *LocalOfferSent) ProcessEvent(event ProtocolEvent, env *Environment,
859
) (*CloseStateTransition, error) {
4✔
860

4✔
861
        switch msg := event.(type) { //nolint:gocritic
4✔
862
        // If we receive a LocalSigReceived event, then we'll attempt to
863
        // validate the signature from the remote party. If valid, then we can
864
        // broadcast the transaction, and transition to the ClosePending state.
865
        case *LocalSigReceived:
4✔
866
                // Extract and validate that only one sig field is set.
4✔
867
                sig, err := extractSig(msg.SigMsg).Unpack()
4✔
868
                if err != nil {
5✔
869
                        return nil, err
1✔
870
                }
1✔
871

872
                remoteSig, err := sig.ToSignature()
3✔
873
                if err != nil {
3✔
874
                        return nil, err
×
875
                }
×
876
                localSig, err := l.LocalSig.ToSignature()
3✔
877
                if err != nil {
3✔
878
                        return nil, err
×
879
                }
×
880

881
                // Now that we have their signature, we'll attempt to validate
882
                // it, then extract a valid closing signature from it.
883
                closeTx, _, err := env.CloseSigner.CompleteCooperativeClose(
3✔
884
                        localSig, remoteSig, l.LocalDeliveryScript,
3✔
885
                        l.RemoteDeliveryScript, l.ProposedFee,
3✔
886
                        lnwallet.WithCustomSequence(mempool.MaxRBFSequence),
3✔
887
                        lnwallet.WithCustomPayer(lntypes.Local),
3✔
888
                )
3✔
889
                if err != nil {
3✔
890
                        return nil, err
×
891
                }
×
892

893
                // As we're about to broadcast a new version of the co-op close
894
                // transaction, we'll mark again as broadcast, but with this
895
                // variant of the co-op close tx.
896
                err = env.ChanObserver.MarkCoopBroadcasted(closeTx, true)
3✔
897
                if err != nil {
3✔
898
                        return nil, err
×
899
                }
×
900

901
                broadcastEvent := protofsm.DaemonEventSet{&protofsm.BroadcastTxn{ //nolint:ll
3✔
902
                        Tx: closeTx,
3✔
903
                        Label: labels.MakeLabel(
3✔
904
                                labels.LabelTypeChannelClose, &env.Scid,
3✔
905
                        ),
3✔
906
                }}
3✔
907

3✔
908
                chancloserLog.Infof("ChannelPoint(%v): received sig from "+
3✔
909
                        "remote party, broadcasting: tx=%v", env.ChanPoint,
3✔
910
                        lnutils.SpewLogClosure(closeTx),
3✔
911
                )
3✔
912

3✔
913
                return &CloseStateTransition{
3✔
914
                        NextState: &ClosePending{
3✔
915
                                CloseTx:           closeTx,
3✔
916
                                FeeRate:           l.ProposedFeeRate,
3✔
917
                                CloseChannelTerms: l.CloseChannelTerms,
3✔
918
                                Party:             lntypes.Local,
3✔
919
                        },
3✔
920
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
3✔
921
                                ExternalEvents: broadcastEvent,
3✔
922
                        }),
3✔
923
                }, nil
3✔
924
        }
925

926
        return nil, fmt.Errorf("%w: received %T while in LocalOfferSent",
×
927
                ErrInvalidStateTransition, event)
×
928
}
929

930
// ProcessEvent implements the state transition function for the
931
// RemoteCloseStart. In this state, we'll wait for the remote party to send a
932
// closing_complete message. Assuming they can pay for the fees, we'll sign it
933
// ourselves, then transition to the next state of ClosePending.
934
func (l *RemoteCloseStart) ProcessEvent(event ProtocolEvent, env *Environment,
935
) (*CloseStateTransition, error) {
6✔
936

6✔
937
        switch msg := event.(type) { //nolint:gocritic
6✔
938
        // If we receive a OfferReceived event, we'll make sure they can
939
        // actually pay for the fee. If so, then we'll counter sign and
940
        // transition to a terminal state.
941
        case *OfferReceivedEvent:
6✔
942
                // To start, we'll perform some basic validation of the sig
6✔
943
                // message they've sent. We'll validate that the remote party
6✔
944
                // actually has enough fees to pay the closing fees.
6✔
945
                if !l.RemoteCanPayFees(msg.SigMsg.FeeSatoshis) {
7✔
946
                        return nil, fmt.Errorf("%w: %v vs %v",
1✔
947
                                ErrRemoteCannotPay,
1✔
948
                                msg.SigMsg.FeeSatoshis,
1✔
949
                                l.RemoteBalance.ToSatoshis())
1✔
950
                }
1✔
951

952
                // With the basic sanity checks out of the way, we'll now
953
                // figure out which signature that we'll attempt to sign
954
                // against.
955
                var (
5✔
956
                        remoteSig input.Signature
5✔
957
                        noClosee  bool
5✔
958
                )
5✔
959
                switch {
5✔
960
                // If our balance is dust, then we expect the CloserNoClosee
961
                // sig to be set.
962
                case l.LocalAmtIsDust():
1✔
963
                        if msg.SigMsg.CloserNoClosee.IsNone() {
2✔
964
                                return nil, ErrCloserNoClosee
1✔
965
                        }
1✔
966
                        msg.SigMsg.CloserNoClosee.WhenSomeV(func(s lnwire.Sig) {
×
967
                                remoteSig, _ = s.ToSignature()
×
968
                                noClosee = true
×
969
                        })
×
970

971
                // Otherwise, we'll assume that CloseAndClosee is set.
972
                //
973
                // TODO(roasbeef): NoCloserClosee, but makes no sense?
974
                default:
4✔
975
                        if msg.SigMsg.CloserAndClosee.IsNone() {
5✔
976
                                return nil, ErrCloserAndClosee
1✔
977
                        }
1✔
978
                        msg.SigMsg.CloserAndClosee.WhenSomeV(func(s lnwire.Sig) { //nolint:ll
6✔
979
                                remoteSig, _ = s.ToSignature()
3✔
980
                        })
3✔
981
                }
982

983
                chanOpts := []lnwallet.ChanCloseOpt{
3✔
984
                        lnwallet.WithCustomSequence(mempool.MaxRBFSequence),
3✔
985
                        lnwallet.WithCustomLockTime(msg.SigMsg.LockTime),
3✔
986
                        lnwallet.WithCustomPayer(lntypes.Remote),
3✔
987
                }
3✔
988

3✔
989
                chancloserLog.Infof("responding to close w/ local_addr=%x, "+
3✔
990
                        "remote_addr=%x, fee=%v",
3✔
991
                        l.LocalDeliveryScript[:], l.RemoteDeliveryScript[:],
3✔
992
                        msg.SigMsg.FeeSatoshis)
3✔
993

3✔
994
                // Now that we have the remote sig, we'll sign the version they
3✔
995
                // signed, then attempt to complete the cooperative close
3✔
996
                // process.
3✔
997
                //
3✔
998
                // TODO(roasbeef): need to be able to omit an output when
3✔
999
                // signing based on the above, as closing opt
3✔
1000
                rawSig, _, _, err := env.CloseSigner.CreateCloseProposal(
3✔
1001
                        msg.SigMsg.FeeSatoshis, l.LocalDeliveryScript,
3✔
1002
                        l.RemoteDeliveryScript, chanOpts...,
3✔
1003
                )
3✔
1004
                if err != nil {
3✔
1005
                        return nil, err
×
1006
                }
×
1007
                wireSig, err := lnwire.NewSigFromSignature(rawSig)
3✔
1008
                if err != nil {
3✔
1009
                        return nil, err
×
1010
                }
×
1011

1012
                localSig, err := wireSig.ToSignature()
3✔
1013
                if err != nil {
3✔
1014
                        return nil, err
×
1015
                }
×
1016

1017
                // With our signature created, we'll now attempt to finalize the
1018
                // close process.
1019
                closeTx, _, err := env.CloseSigner.CompleteCooperativeClose(
3✔
1020
                        localSig, remoteSig, l.LocalDeliveryScript,
3✔
1021
                        l.RemoteDeliveryScript, msg.SigMsg.FeeSatoshis,
3✔
1022
                        chanOpts...,
3✔
1023
                )
3✔
1024
                if err != nil {
3✔
1025
                        return nil, err
×
1026
                }
×
1027

1028
                chancloserLog.Infof("ChannelPoint(%v): received sig (fee=%v "+
3✔
1029
                        "sats) from remote party, signing new tx=%v",
3✔
1030
                        env.ChanPoint, msg.SigMsg.FeeSatoshis,
3✔
1031
                        lnutils.SpewLogClosure(closeTx),
3✔
1032
                )
3✔
1033

3✔
1034
                var closingSigs lnwire.ClosingSigs
3✔
1035
                if noClosee {
3✔
1036
                        closingSigs.CloserNoClosee = newSigTlv[tlv.TlvType1](
×
1037
                                wireSig,
×
1038
                        )
×
1039
                } else {
3✔
1040
                        closingSigs.CloserAndClosee = newSigTlv[tlv.TlvType3](
3✔
1041
                                wireSig,
3✔
1042
                        )
3✔
1043
                }
3✔
1044

1045
                // As we're about to broadcast a new version of the co-op close
1046
                // transaction, we'll mark again as broadcast, but with this
1047
                // variant of the co-op close tx.
1048
                //
1049
                // TODO(roasbeef): db will only store one instance, store both?
1050
                err = env.ChanObserver.MarkCoopBroadcasted(closeTx, false)
3✔
1051
                if err != nil {
3✔
1052
                        return nil, err
×
1053
                }
×
1054

1055
                // As we transition, we'll omit two events: one to broadcast
1056
                // the transaction, and the other to send our ClosingSig
1057
                // message to the remote party.
1058
                sendEvent := &protofsm.SendMsgEvent[ProtocolEvent]{
3✔
1059
                        TargetPeer: env.ChanPeer,
3✔
1060
                        Msgs: []lnwire.Message{&lnwire.ClosingSig{
3✔
1061
                                ChannelID:    env.ChanID,
3✔
1062
                                CloserScript: l.RemoteDeliveryScript,
3✔
1063
                                CloseeScript: l.LocalDeliveryScript,
3✔
1064
                                FeeSatoshis:  msg.SigMsg.FeeSatoshis,
3✔
1065
                                LockTime:     msg.SigMsg.LockTime,
3✔
1066
                                ClosingSigs:  closingSigs,
3✔
1067
                        }},
3✔
1068
                }
3✔
1069
                broadcastEvent := &protofsm.BroadcastTxn{
3✔
1070
                        Tx: closeTx,
3✔
1071
                        Label: labels.MakeLabel(
3✔
1072
                                labels.LabelTypeChannelClose, &env.Scid,
3✔
1073
                        ),
3✔
1074
                }
3✔
1075
                daemonEvents := protofsm.DaemonEventSet{
3✔
1076
                        sendEvent, broadcastEvent,
3✔
1077
                }
3✔
1078

3✔
1079
                // We'll also compute the final fee rate that the remote party
3✔
1080
                // paid based off the absolute fee and the size of the closing
3✔
1081
                // transaction.
3✔
1082
                vSize := mempool.GetTxVirtualSize(btcutil.NewTx(closeTx))
3✔
1083
                feeRate := chainfee.SatPerVByte(
3✔
1084
                        int64(msg.SigMsg.FeeSatoshis) / vSize,
3✔
1085
                )
3✔
1086

3✔
1087
                // Now that we've extracted the signature, we'll transition to
3✔
1088
                // the next state where we'll sign+broadcast the sig.
3✔
1089
                return &CloseStateTransition{
3✔
1090
                        NextState: &ClosePending{
3✔
1091
                                CloseTx:           closeTx,
3✔
1092
                                FeeRate:           feeRate,
3✔
1093
                                CloseChannelTerms: l.CloseChannelTerms,
3✔
1094
                                Party:             lntypes.Remote,
3✔
1095
                        },
3✔
1096
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
3✔
1097
                                ExternalEvents: daemonEvents,
3✔
1098
                        }),
3✔
1099
                }, nil
3✔
1100
        }
1101

1102
        return nil, fmt.Errorf("%w: received %T while in RemoteCloseStart",
×
1103
                ErrInvalidStateTransition, event)
×
1104
}
1105

1106
// ProcessEvent is a semi-terminal state in the rbf-coop close state machine.
1107
// In this state, we're waiting for either a confirmation, or for either side
1108
// to attempt to create a new RBF'd co-op close transaction.
1109
func (c *ClosePending) ProcessEvent(event ProtocolEvent, env *Environment,
1110
) (*CloseStateTransition, error) {
2✔
1111

2✔
1112
        switch msg := event.(type) {
2✔
1113
        // If we can a spend while waiting for the close, then we'll go to our
1114
        // terminal state.
1115
        case *SpendEvent:
×
1116
                return &CloseStateTransition{
×
1117
                        NextState: &CloseFin{
×
1118
                                ConfirmedTx: msg.Tx,
×
1119
                        },
×
1120
                }, nil
×
1121

1122
        // If we get a send offer event in this state, then we're doing a state
1123
        // transition to the LocalCloseStart state, so we can sign a new closing
1124
        // tx.
1125
        case *SendOfferEvent:
1✔
1126
                return &CloseStateTransition{
1✔
1127
                        NextState: &LocalCloseStart{
1✔
1128
                                CloseChannelTerms: c.CloseChannelTerms,
1✔
1129
                        },
1✔
1130
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
1✔
1131
                                InternalEvent: []ProtocolEvent{msg},
1✔
1132
                        }),
1✔
1133
                }, nil
1✔
1134

1135
        // If we get an offer received event, then we're doing a state
1136
        // transition to the RemoteCloseStart, as the remote peer wants to sign
1137
        // a new closing tx.
1138
        case *OfferReceivedEvent:
1✔
1139
                return &CloseStateTransition{
1✔
1140
                        NextState: &RemoteCloseStart{
1✔
1141
                                CloseChannelTerms: c.CloseChannelTerms,
1✔
1142
                        },
1✔
1143
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
1✔
1144
                                InternalEvent: []ProtocolEvent{msg},
1✔
1145
                        }),
1✔
1146
                }, nil
1✔
1147

1148
        default:
×
1149

×
1150
                return &CloseStateTransition{
×
1151
                        NextState: c,
×
1152
                }, nil
×
1153
        }
1154
}
1155

1156
// ProcessEvent is the event processing for out terminal state. In this state,
1157
// we just keep looping back on ourselves.
1158
func (c *CloseFin) ProcessEvent(event ProtocolEvent, env *Environment,
1159
) (*CloseStateTransition, error) {
×
1160

×
1161
        return &CloseStateTransition{
×
1162
                NextState: c,
×
1163
        }, nil
×
1164
}
×
1165

1166
// ProcessEvent is a semi-terminal state in the rbf-coop close state machine.
1167
// In this state, we hit a validation error in an earlier state, so we'll remain
1168
// in this state for the user to examine. We may also process new requests to
1169
// continue the state machine.
1170
func (c *CloseErr) ProcessEvent(event ProtocolEvent, env *Environment,
NEW
1171
) (*CloseStateTransition, error) {
×
NEW
1172

×
NEW
1173
        switch msg := event.(type) {
×
1174
        // If we can a spend while waiting for the close, then we'll go to our
1175
        // terminal state.
NEW
1176
        case *SpendEvent:
×
NEW
1177
                return &CloseStateTransition{
×
NEW
1178
                        NextState: &CloseFin{
×
NEW
1179
                                ConfirmedTx: msg.Tx,
×
NEW
1180
                        },
×
NEW
1181
                }, nil
×
1182

1183
        // If we get a send offer event in this state, then we're doing a state
1184
        // transition to the LocalCloseStart state, so we can sign a new closing
1185
        // tx.
NEW
1186
        case *SendOfferEvent:
×
NEW
1187
                return &CloseStateTransition{
×
NEW
1188
                        NextState: &LocalCloseStart{
×
NEW
1189
                                CloseChannelTerms: c.CloseChannelTerms,
×
NEW
1190
                        },
×
NEW
1191
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
×
NEW
1192
                                InternalEvent: []ProtocolEvent{msg},
×
NEW
1193
                        }),
×
NEW
1194
                }, nil
×
1195

1196
        // If we get an offer received event, then we're doing a state
1197
        // transition to the RemoteCloseStart, as the remote peer wants to sign
1198
        // a new closing tx.
NEW
1199
        case *OfferReceivedEvent:
×
NEW
1200
                return &CloseStateTransition{
×
NEW
1201
                        NextState: &RemoteCloseStart{
×
NEW
1202
                                CloseChannelTerms: c.CloseChannelTerms,
×
NEW
1203
                        },
×
NEW
1204
                        NewEvents: fn.Some(protofsm.EmittedEvent[ProtocolEvent]{
×
NEW
1205
                                InternalEvent: []ProtocolEvent{msg},
×
NEW
1206
                        }),
×
NEW
1207
                }, nil
×
1208

NEW
1209
        default:
×
NEW
1210
                return &CloseStateTransition{
×
NEW
1211
                        NextState: c,
×
NEW
1212
                }, nil
×
1213
        }
1214
}
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