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

lightningnetwork / lnd / 13980885714

20 Mar 2025 10:53PM UTC coverage: 58.613% (-10.2%) from 68.789%
13980885714

Pull #9623

github

web-flow
Merge 9eaec1f7a into 09b674508
Pull Request #9623: Size msg test msg

0 of 1572 new or added lines in 42 files covered. (0.0%)

27755 existing lines in 442 files now uncovered.

96886 of 165299 relevant lines covered (58.61%)

1.82 hits per line

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

79.06
/contractcourt/chain_watcher.go
1
package contractcourt
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "slices"
7
        "sync"
8
        "sync/atomic"
9
        "time"
10

11
        "github.com/btcsuite/btcd/btcec/v2"
12
        "github.com/btcsuite/btcd/btcutil"
13
        "github.com/btcsuite/btcd/chaincfg"
14
        "github.com/btcsuite/btcd/chaincfg/chainhash"
15
        "github.com/btcsuite/btcd/mempool"
16
        "github.com/btcsuite/btcd/txscript"
17
        "github.com/btcsuite/btcd/wire"
18
        "github.com/davecgh/go-spew/spew"
19
        "github.com/lightningnetwork/lnd/chainio"
20
        "github.com/lightningnetwork/lnd/chainntnfs"
21
        "github.com/lightningnetwork/lnd/channeldb"
22
        "github.com/lightningnetwork/lnd/fn/v2"
23
        "github.com/lightningnetwork/lnd/input"
24
        "github.com/lightningnetwork/lnd/lntypes"
25
        "github.com/lightningnetwork/lnd/lnutils"
26
        "github.com/lightningnetwork/lnd/lnwallet"
27
        "github.com/lightningnetwork/lnd/lnwire"
28
)
29

30
const (
31
        // minCommitPointPollTimeout is the minimum time we'll wait before
32
        // polling the database for a channel's commitpoint.
33
        minCommitPointPollTimeout = 1 * time.Second
34

35
        // maxCommitPointPollTimeout is the maximum time we'll wait before
36
        // polling the database for a channel's commitpoint.
37
        maxCommitPointPollTimeout = 10 * time.Minute
38
)
39

40
// LocalUnilateralCloseInfo encapsulates all the information we need to act on
41
// a local force close that gets confirmed.
42
type LocalUnilateralCloseInfo struct {
43
        *chainntnfs.SpendDetail
44
        *lnwallet.LocalForceCloseSummary
45
        *channeldb.ChannelCloseSummary
46

47
        // CommitSet is the set of known valid commitments at the time the
48
        // remote party's commitment hit the chain.
49
        CommitSet CommitSet
50
}
51

52
// CooperativeCloseInfo encapsulates all the information we need to act on a
53
// cooperative close that gets confirmed.
54
type CooperativeCloseInfo struct {
55
        *channeldb.ChannelCloseSummary
56
}
57

58
// RemoteUnilateralCloseInfo wraps the normal UnilateralCloseSummary to couple
59
// the CommitSet at the time of channel closure.
60
type RemoteUnilateralCloseInfo struct {
61
        *lnwallet.UnilateralCloseSummary
62

63
        // CommitSet is the set of known valid commitments at the time the
64
        // remote party's commitment hit the chain.
65
        CommitSet CommitSet
66
}
67

68
// BreachResolution wraps the outpoint of the breached channel.
69
type BreachResolution struct {
70
        FundingOutPoint wire.OutPoint
71
}
72

73
// BreachCloseInfo wraps the BreachResolution with a CommitSet for the latest,
74
// non-breached state, with the AnchorResolution for the breached state.
75
type BreachCloseInfo struct {
76
        *BreachResolution
77
        *lnwallet.AnchorResolution
78

79
        // CommitHash is the hash of the commitment transaction.
80
        CommitHash chainhash.Hash
81

82
        // CommitSet is the set of known valid commitments at the time the
83
        // breach occurred on-chain.
84
        CommitSet CommitSet
85

86
        // CloseSummary gives the recipient of the BreachCloseInfo information
87
        // to mark the channel closed in the database.
88
        CloseSummary channeldb.ChannelCloseSummary
89
}
90

91
// CommitSet is a collection of the set of known valid commitments at a given
92
// instant. If ConfCommitKey is set, then the commitment identified by the
93
// HtlcSetKey has hit the chain. This struct will be used to examine all live
94
// HTLCs to determine if any additional actions need to be made based on the
95
// remote party's commitments.
96
type CommitSet struct {
97
        // When the ConfCommitKey is set, it signals that the commitment tx was
98
        // confirmed in the chain.
99
        ConfCommitKey fn.Option[HtlcSetKey]
100

101
        // HtlcSets stores the set of all known active HTLC for each active
102
        // commitment at the time of channel closure.
103
        HtlcSets map[HtlcSetKey][]channeldb.HTLC
104
}
105

106
// IsEmpty returns true if there are no HTLCs at all within all commitments
107
// that are a part of this commitment diff.
108
func (c *CommitSet) IsEmpty() bool {
3✔
109
        if c == nil {
3✔
110
                return true
×
111
        }
×
112

113
        for _, htlcs := range c.HtlcSets {
6✔
114
                if len(htlcs) != 0 {
6✔
115
                        return false
3✔
116
                }
3✔
117
        }
118

119
        return true
3✔
120
}
121

122
// toActiveHTLCSets returns the set of all active HTLCs across all commitment
123
// transactions.
124
func (c *CommitSet) toActiveHTLCSets() map[HtlcSetKey]htlcSet {
3✔
125
        htlcSets := make(map[HtlcSetKey]htlcSet)
3✔
126

3✔
127
        for htlcSetKey, htlcs := range c.HtlcSets {
6✔
128
                htlcSets[htlcSetKey] = newHtlcSet(htlcs)
3✔
129
        }
3✔
130

131
        return htlcSets
3✔
132
}
133

134
// ChainEventSubscription is a struct that houses a subscription to be notified
135
// for any on-chain events related to a channel. There are three types of
3✔
136
// possible on-chain events: a cooperative channel closure, a unilateral
6✔
137
// channel closure, and a channel breach. The fourth type: a force close is
3✔
138
// locally initiated, so we don't provide any event stream for said event.
3✔
139
type ChainEventSubscription struct {
140
        // ChanPoint is that channel that chain events will be dispatched for.
141
        ChanPoint wire.OutPoint
3✔
142

6✔
143
        // RemoteUnilateralClosure is a channel that will be sent upon in the
3✔
144
        // event that the remote party's commitment transaction is confirmed.
3✔
145
        RemoteUnilateralClosure chan *RemoteUnilateralCloseInfo
146

147
        // LocalUnilateralClosure is a channel that will be sent upon in the
3✔
148
        // event that our commitment transaction is confirmed.
6✔
149
        LocalUnilateralClosure chan *LocalUnilateralCloseInfo
3✔
150

3✔
151
        // CooperativeClosure is a signal that will be sent upon once a
6✔
152
        // cooperative channel closure has been detected confirmed.
3✔
153
        CooperativeClosure chan *CooperativeCloseInfo
3✔
154

155
        // ContractBreach is a channel that will be sent upon if we detect a
156
        // contract breach. The struct sent across the channel contains all the
3✔
157
        // material required to bring the cheating channel peer to justice.
3✔
158
        ContractBreach chan *BreachCloseInfo
159

160
        // Cancel cancels the subscription to the event stream for a particular
3✔
161
        // channel. This method should be called once the caller no longer needs to
162
        // be notified of any on-chain events for a particular channel.
163
        Cancel func()
164
}
165

166
// chainWatcherConfig encapsulates all the necessary functions and interfaces
167
// needed to watch and act on on-chain events for a particular channel.
168
type chainWatcherConfig struct {
169
        // chanState is a snapshot of the persistent state of the channel that
170
        // we're watching. In the event of an on-chain event, we'll query the
171
        // database to ensure that we act using the most up to date state.
172
        chanState *channeldb.OpenChannel
173

174
        // notifier is a reference to the channel notifier that we'll use to be
175
        // notified of output spends and when transactions are confirmed.
176
        notifier chainntnfs.ChainNotifier
177

178
        // signer is the main signer instances that will be responsible for
179
        // signing any HTLC and commitment transaction generated by the state
180
        // machine.
181
        signer input.Signer
182

183
        // contractBreach is a method that will be called by the watcher if it
184
        // detects that a contract breach transaction has been confirmed. It
185
        // will only return a non-nil error when the BreachArbitrator has
186
        // preserved the necessary breach info for this channel point.
187
        contractBreach func(*lnwallet.BreachRetribution) error
188

189
        // isOurAddr is a function that returns true if the passed address is
190
        // known to us.
191
        isOurAddr func(btcutil.Address) bool
192

193
        // extractStateNumHint extracts the encoded state hint using the passed
194
        // obfuscater. This is used by the chain watcher to identify which
195
        // state was broadcast and confirmed on-chain.
196
        extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64
197

198
        // auxLeafStore can be used to fetch information for custom channels.
199
        auxLeafStore fn.Option[lnwallet.AuxLeafStore]
200

201
        // auxResolver is used to supplement contract resolution.
202
        auxResolver fn.Option[lnwallet.AuxContractResolver]
203
}
204

205
// chainWatcher is a system that's assigned to every active channel. The duty
206
// of this system is to watch the chain for spends of the channels chan point.
207
// If a spend is detected then with chain watcher will notify all subscribers
208
// that the channel has been closed, and also give them the materials necessary
209
// to sweep the funds of the channel on chain eventually.
210
type chainWatcher struct {
211
        started int32 // To be used atomically.
212
        stopped int32 // To be used atomically.
213

214
        // Embed the blockbeat consumer struct to get access to the method
215
        // `NotifyBlockProcessed` and the `BlockbeatChan`.
216
        chainio.BeatConsumer
217

218
        quit chan struct{}
219
        wg   sync.WaitGroup
220

221
        cfg chainWatcherConfig
222

223
        // stateHintObfuscator is a 48-bit state hint that's used to obfuscate
224
        // the current state number on the commitment transactions.
225
        stateHintObfuscator [lnwallet.StateHintSize]byte
226

227
        // All the fields below are protected by this mutex.
228
        sync.Mutex
229

230
        // clientID is an ephemeral counter used to keep track of each
231
        // individual client subscription.
232
        clientID uint64
233

234
        // clientSubscriptions is a map that keeps track of all the active
235
        // client subscriptions for events related to this channel.
236
        clientSubscriptions map[uint64]*ChainEventSubscription
237

238
        // fundingSpendNtfn is the spending notification subscription for the
239
        // funding outpoint.
240
        fundingSpendNtfn *chainntnfs.SpendEvent
241

242
        // fundingConfirmedNtfn is the confirmation notification subscription
243
        // for the funding outpoint. This is only created if the channel is
244
        // both taproot and pending confirmation.
245
        //
246
        // For taproot pkscripts, `RegisterSpendNtfn` will only notify on the
247
        // outpoint being spent and not the outpoint+pkscript due to
248
        // `ComputePkScript` being unable to compute the pkscript if a key
249
        // spend is used. We need to add a `RegisterConfirmationsNtfn` here to
250
        // ensure that the outpoint+pkscript pair is confirmed before calling
251
        // `RegisterSpendNtfn`.
252
        fundingConfirmedNtfn *chainntnfs.ConfirmationEvent
253
}
254

255
// newChainWatcher returns a new instance of a chainWatcher for a channel given
256
// the chan point to watch, and also a notifier instance that will allow us to
257
// detect on chain events.
258
func newChainWatcher(cfg chainWatcherConfig) (*chainWatcher, error) {
259
        // In order to be able to detect the nature of a potential channel
260
        // closure we'll need to reconstruct the state hint bytes used to
261
        // obfuscate the commitment state number encoded in the lock time and
262
        // sequence fields.
263
        var stateHint [lnwallet.StateHintSize]byte
264
        chanState := cfg.chanState
265
        if chanState.IsInitiator {
266
                stateHint = lnwallet.DeriveStateHintObfuscator(
267
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
268
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
269
                )
270
        } else {
271
                stateHint = lnwallet.DeriveStateHintObfuscator(
272
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
273
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
274
                )
275
        }
276

277
        // Get the witness script for the funding output.
278
        fundingPkScript, err := deriveFundingPkScript(chanState)
279
        if err != nil {
280
                return nil, err
281
        }
282

283
        // Get the channel opening block height.
284
        heightHint := chanState.DeriveHeightHint()
285

286
        // We'll register for a notification to be dispatched if the funding
287
        // output is spent.
3✔
288
        spendNtfn, err := cfg.notifier.RegisterSpendNtfn(
3✔
289
                &chanState.FundingOutpoint, fundingPkScript, heightHint,
3✔
290
        )
3✔
291
        if err != nil {
3✔
292
                return nil, err
3✔
293
        }
3✔
294

6✔
295
        c := &chainWatcher{
3✔
296
                cfg:                 cfg,
3✔
297
                stateHintObfuscator: stateHint,
3✔
298
                quit:                make(chan struct{}),
3✔
299
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
6✔
300
                fundingSpendNtfn:    spendNtfn,
3✔
301
        }
3✔
302

3✔
303
        // If this is a pending taproot channel, we need to register for a
3✔
304
        // confirmation notification of the funding tx. Check the docs in
3✔
305
        // `fundingConfirmedNtfn` for details.
306
        if c.cfg.chanState.IsPending && c.cfg.chanState.ChanType.IsTaproot() {
307
                confNtfn, err := cfg.notifier.RegisterConfirmationsNtfn(
3✔
308
                        &chanState.FundingOutpoint.Hash, fundingPkScript, 1,
3✔
UNCOV
309
                        heightHint,
×
UNCOV
310
                )
×
311
                if err != nil {
312
                        return nil, err
313
                }
3✔
314

3✔
315
                c.fundingConfirmedNtfn = confNtfn
3✔
316
        }
3✔
317

3✔
318
        // Mount the block consumer.
3✔
319
        c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
3✔
320

3✔
UNCOV
321
        return c, nil
×
UNCOV
322
}
×
323

324
// Compile-time check for the chainio.Consumer interface.
3✔
325
var _ chainio.Consumer = (*chainWatcher)(nil)
3✔
326

3✔
327
// Name returns the name of the watcher.
3✔
328
//
3✔
329
// NOTE: part of the `chainio.Consumer` interface.
3✔
330
func (c *chainWatcher) Name() string {
3✔
331
        return fmt.Sprintf("ChainWatcher(%v)", c.cfg.chanState.FundingOutpoint)
3✔
332
}
3✔
333

3✔
334
// Start starts all goroutines that the chainWatcher needs to perform its
3✔
335
// duties.
6✔
336
func (c *chainWatcher) Start() error {
3✔
337
        if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
3✔
338
                return nil
3✔
339
        }
3✔
340

3✔
UNCOV
341
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
×
UNCOV
342
                c.cfg.chanState.FundingOutpoint)
×
343

344
        c.wg.Add(1)
3✔
345
        go c.closeObserver()
346

347
        return nil
348
}
3✔
349

3✔
350
// Stop signals the close observer to gracefully exit.
3✔
351
func (c *chainWatcher) Stop() error {
352
        if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
353
                return nil
354
        }
355

356
        close(c.quit)
357

358
        c.wg.Wait()
359

3✔
360
        return nil
3✔
361
}
3✔
362

363
// SubscribeChannelEvents returns an active subscription to the set of channel
364
// events for the channel watched by this chain watcher. Once clients no longer
365
// require the subscription, they should call the Cancel() method to allow the
3✔
366
// watcher to regain those committed resources.
3✔
UNCOV
367
func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
×
UNCOV
368

×
369
        c.Lock()
370
        clientID := c.clientID
3✔
371
        c.clientID++
3✔
372
        c.Unlock()
3✔
373

3✔
374
        log.Debugf("New ChainEventSubscription(id=%v) for ChannelPoint(%v)",
3✔
375
                clientID, c.cfg.chanState.FundingOutpoint)
3✔
376

3✔
377
        sub := &ChainEventSubscription{
378
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
379
                RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
380
                LocalUnilateralClosure:  make(chan *LocalUnilateralCloseInfo, 1),
3✔
381
                CooperativeClosure:      make(chan *CooperativeCloseInfo, 1),
3✔
UNCOV
382
                ContractBreach:          make(chan *BreachCloseInfo, 1),
×
UNCOV
383
                Cancel: func() {
×
384
                        c.Lock()
385
                        delete(c.clientSubscriptions, clientID)
3✔
386
                        c.Unlock()
3✔
387
                },
3✔
388
        }
3✔
389

3✔
390
        c.Lock()
391
        c.clientSubscriptions[clientID] = sub
392
        c.Unlock()
393

394
        return sub
395
}
396

3✔
397
// handleUnknownLocalState checks whether the passed spend _could_ be a local
3✔
398
// state that for some reason is unknown to us. This could be a state published
3✔
399
// by us before we lost state, which we will try to sweep. Or it could be one
3✔
400
// of our revoked states that somehow made it to the chain. If that's the case
3✔
401
// we cannot really hope that we'll be able to get our money back, but we'll
3✔
402
// try to sweep it anyway. If this is not an unknown local state, false is
3✔
403
// returned.
3✔
404
func (c *chainWatcher) handleUnknownLocalState(
3✔
405
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
3✔
406
        chainSet *chainSet) (bool, error) {
3✔
407

3✔
408
        // If the spend was a local commitment, at this point it must either be
3✔
409
        // a past state (we breached!) or a future state (we lost state!). In
3✔
410
        // either case, the only thing we can do is to attempt to sweep what is
3✔
411
        // there.
3✔
412

6✔
413
        // First, we'll re-derive our commitment point for this state since
3✔
414
        // this is what we use to randomize each of the keys for this state.
3✔
415
        commitSecret, err := c.cfg.chanState.RevocationProducer.AtIndex(
3✔
416
                broadcastStateNum,
3✔
417
        )
418
        if err != nil {
419
                return false, err
3✔
420
        }
3✔
421
        commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
3✔
422

3✔
423
        // Now that we have the commit point, we'll derive the tweaked local
3✔
424
        // and remote keys for this state. We use our point as only we can
425
        // revoke our own commitment.
426
        commitKeyRing := lnwallet.DeriveCommitmentKeys(
427
                commitPoint, lntypes.Local, c.cfg.chanState.ChanType,
428
                &c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
429
        )
430

431
        auxResult, err := fn.MapOptionZ(
432
                c.cfg.auxLeafStore,
433
                //nolint:ll
434
                func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
435
                        return s.FetchLeavesFromCommit(
3✔
436
                                lnwallet.NewAuxChanState(c.cfg.chanState),
3✔
437
                                c.cfg.chanState.LocalCommitment, *commitKeyRing,
3✔
438
                                lntypes.Local,
3✔
439
                        )
3✔
440
                },
3✔
441
        ).Unpack()
3✔
442
        if err != nil {
3✔
443
                return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
3✔
444
        }
3✔
445

3✔
446
        // With the keys derived, we'll construct the remote script that'll be
3✔
447
        // present if they have a non-dust balance on the commitment.
3✔
UNCOV
448
        var leaseExpiry uint32
×
UNCOV
449
        if c.cfg.chanState.ChanType.HasLeaseExpiration() {
×
450
                leaseExpiry = c.cfg.chanState.ThawHeight
3✔
451
        }
3✔
452

3✔
453
        remoteAuxLeaf := fn.FlatMapOption(
3✔
454
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
3✔
455
                        return l.RemoteAuxLeaf
3✔
456
                },
3✔
457
        )(auxResult.AuxLeaves)
3✔
458
        remoteScript, _, err := lnwallet.CommitScriptToRemote(
3✔
459
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
3✔
460
                commitKeyRing.ToRemoteKey, leaseExpiry,
3✔
461
                remoteAuxLeaf,
3✔
462
        )
3✔
463
        if err != nil {
3✔
464
                return false, err
×
465
        }
×
UNCOV
466

×
UNCOV
467
        // Next, we'll derive our script that includes the revocation base for
×
UNCOV
468
        // the remote party allowing them to claim this output before the CSV
×
UNCOV
469
        // delay if we breach.
×
470
        localAuxLeaf := fn.FlatMapOption(
471
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
3✔
472
                        return l.LocalAuxLeaf
×
473
                },
×
474
        )(auxResult.AuxLeaves)
475
        localScript, err := lnwallet.CommitScriptToSelf(
476
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
477
                commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
3✔
478
                uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
6✔
479
                localAuxLeaf,
3✔
480
        )
3✔
481
        if err != nil {
482
                return false, err
3✔
483
        }
3✔
UNCOV
484

×
UNCOV
485
        // With all our scripts assembled, we'll examine the outputs of the
×
486
        // commitment transaction to determine if this is a local force close
487
        // or not.
3✔
488
        ourCommit := false
3✔
489
        for _, output := range commitSpend.SpendingTx.TxOut {
3✔
490
                pkScript := output.PkScript
3✔
491

3✔
492
                switch {
3✔
UNCOV
493
                case bytes.Equal(localScript.PkScript(), pkScript):
×
UNCOV
494
                        ourCommit = true
×
495

496
                case bytes.Equal(remoteScript.PkScript(), pkScript):
497
                        ourCommit = true
498
                }
499
        }
3✔
500

3✔
UNCOV
501
        // If the script is not present, this cannot be our commit.
×
UNCOV
502
        if !ourCommit {
×
503
                return false, nil
504
        }
3✔
505

3✔
506
        log.Warnf("Detected local unilateral close of unknown state %v "+
3✔
507
                "(our state=%v)", broadcastStateNum,
3✔
508
                chainSet.localCommit.CommitHeight)
3✔
509

3✔
510
        // If this is our commitment transaction, then we try to act even
3✔
UNCOV
511
        // though we won't be able to sweep HTLCs.
×
UNCOV
512
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
×
513
        if err := c.dispatchLocalForceClose(
514
                commitSpend, broadcastStateNum, chainSet.commitSet,
515
        ); err != nil {
516
                return false, fmt.Errorf("unable to handle local"+
517
                        "close for chan_point=%v: %v",
3✔
518
                        c.cfg.chanState.FundingOutpoint, err)
6✔
519
        }
3✔
520

3✔
521
        return true, nil
3✔
522
}
3✔
523

3✔
524
// chainSet includes all the information we need to dispatch a channel close
525
// event to any subscribers.
3✔
526
type chainSet struct {
3✔
527
        // remoteStateNum is the commitment number of the lowest valid
528
        // commitment the remote party holds from our PoV. This value is used
529
        // to determine if the remote party is playing a state that's behind,
530
        // in line, or ahead of the latest state we know for it.
531
        remoteStateNum uint64
6✔
532

3✔
533
        // commitSet includes information pertaining to the set of active HTLCs
3✔
534
        // on each commitment.
535
        commitSet CommitSet
3✔
536

3✔
537
        // remoteCommit is the current commitment of the remote party.
3✔
538
        remoteCommit channeldb.ChannelCommitment
3✔
539

3✔
540
        // localCommit is our current commitment.
3✔
541
        localCommit channeldb.ChannelCommitment
3✔
542

3✔
543
        // remotePendingCommit points to the dangling commitment of the remote
3✔
544
        // party, if it exists. If there's no dangling commitment, then this
3✔
UNCOV
545
        // pointer will be nil.
×
UNCOV
546
        remotePendingCommit *channeldb.ChannelCommitment
×
UNCOV
547
}
×
UNCOV
548

×
549
// newChainSet creates a new chainSet given the current up to date channel
550
// state.
3✔
551
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
552
        // First, we'll grab the current unrevoked commitments for ourselves
553
        // and the remote party.
554
        localCommit, remoteCommit, err := chanState.LatestCommitments()
555
        if err != nil {
556
                return nil, fmt.Errorf("unable to fetch channel state for "+
557
                        "chan_point=%v: %v", chanState.FundingOutpoint, err)
558
        }
559

560
        log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
561
                chanState.FundingOutpoint, chanState.ChanType,
562
                spew.Sdump(localCommit))
563
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
564
                chanState.FundingOutpoint, chanState.ChanType,
565
                spew.Sdump(remoteCommit))
566

567
        // Fetch the current known commit height for the remote party, and
568
        // their pending commitment chain tip if it exists.
569
        remoteStateNum := remoteCommit.CommitHeight
570
        remoteChainTip, err := chanState.RemoteCommitChainTip()
571
        if err != nil && err != channeldb.ErrNoPendingCommit {
572
                return nil, fmt.Errorf("unable to obtain chain tip for "+
573
                        "ChannelPoint(%v): %v",
574
                        chanState.FundingOutpoint, err)
575
        }
576

577
        // Now that we have all the possible valid commitments, we'll make the
578
        // CommitSet the ChannelArbitrator will need in order to carry out its
579
        // duty.
580
        commitSet := CommitSet{
3✔
581
                HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
3✔
582
                        LocalHtlcSet:  localCommit.Htlcs,
3✔
583
                        RemoteHtlcSet: remoteCommit.Htlcs,
3✔
584
                },
3✔
UNCOV
585
        }
×
UNCOV
586

×
UNCOV
587
        var remotePendingCommit *channeldb.ChannelCommitment
×
588
        if remoteChainTip != nil {
589
                remotePendingCommit = &remoteChainTip.Commitment
3✔
590
                log.Tracef("ChannelPoint(%v): remote_pending_commit_type=%v, "+
3✔
591
                        "remote_pending_commit=%v", chanState.FundingOutpoint,
3✔
592
                        chanState.ChanType,
3✔
593
                        spew.Sdump(remoteChainTip.Commitment))
3✔
594

3✔
595
                htlcs := remoteChainTip.Commitment.Htlcs
3✔
596
                commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
3✔
597
        }
3✔
598

3✔
599
        // We'll now retrieve the latest state of the revocation store so we
3✔
600
        // can populate the revocation information within the channel state
3✔
UNCOV
601
        // object that we have.
×
UNCOV
602
        //
×
UNCOV
603
        // TODO(roasbeef): mutation is bad mkay
×
UNCOV
604
        _, err = chanState.RemoteRevocationStore()
×
605
        if err != nil {
606
                return nil, fmt.Errorf("unable to fetch revocation state for "+
607
                        "chan_point=%v", chanState.FundingOutpoint)
608
        }
609

3✔
610
        return &chainSet{
3✔
611
                remoteStateNum:      remoteStateNum,
3✔
612
                commitSet:           commitSet,
3✔
613
                localCommit:         *localCommit,
3✔
614
                remoteCommit:        *remoteCommit,
3✔
615
                remotePendingCommit: remotePendingCommit,
3✔
616
        }, nil
3✔
617
}
6✔
618

3✔
619
// closeObserver is a dedicated goroutine that will watch for any closes of the
3✔
620
// channel that it's watching on chain. In the event of an on-chain event, the
3✔
621
// close observer will assembled the proper materials required to claim the
3✔
622
// funds of the channel on-chain (if required), then dispatch these as
3✔
623
// notifications to all subscribers.
3✔
624
func (c *chainWatcher) closeObserver() {
3✔
625
        defer c.wg.Done()
3✔
626
        defer c.fundingSpendNtfn.Cancel()
3✔
627

628
        log.Infof("Close observer for ChannelPoint(%v) active",
629
                c.cfg.chanState.FundingOutpoint)
630

631
        for {
632
                select {
633
                // A new block is received, we will check whether this block
3✔
634
                // contains a spending tx that we are interested in.
3✔
UNCOV
635
                case beat := <-c.BlockbeatChan:
×
UNCOV
636
                        log.Debugf("ChainWatcher(%v) received blockbeat %v",
×
UNCOV
637
                                c.cfg.chanState.FundingOutpoint, beat.Height())
×
638

639
                        // Process the block.
3✔
640
                        c.handleBlockbeat(beat)
3✔
641

3✔
642
                // If the funding outpoint is spent, we now go ahead and handle
3✔
643
                // it. Note that we cannot rely solely on the `block` event
3✔
644
                // above to trigger a close event, as deep down, the receiving
3✔
645
                // of block notifications and the receiving of spending
3✔
646
                // notifications are done in two different goroutines, so the
647
                // expected order: [receive block -> receive spend] is not
648
                // guaranteed .
649
                case spend, ok := <-c.fundingSpendNtfn.Spend:
650
                        // If the channel was closed, then this means that the
651
                        // notifier exited, so we will as well.
652
                        if !ok {
653
                                return
3✔
654
                        }
3✔
655

3✔
656
                        err := c.handleCommitSpend(spend)
3✔
657
                        if err != nil {
3✔
658
                                log.Errorf("Failed to handle commit spend: %v",
3✔
659
                                        err)
3✔
660
                        }
6✔
661

3✔
662
                // The chainWatcher has been signalled to exit, so we'll do so
663
                // now.
664
                case <-c.quit:
3✔
665
                        return
3✔
666
                }
3✔
667
        }
3✔
668
}
3✔
669

3✔
670
// handleKnownLocalState checks whether the passed spend is a local state that
671
// is known to us (the current state). If so we will act on this state using
672
// the passed chainSet. If this is not a known local state, false is returned.
673
func (c *chainWatcher) handleKnownLocalState(
674
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
675
        chainSet *chainSet) (bool, error) {
676

677
        // If the channel is recovered, we won't have a local commit to check
678
        // against, so immediately return.
3✔
679
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
3✔
680
                return false, nil
3✔
681
        }
6✔
682

3✔
683
        commitTxBroadcast := commitSpend.SpendingTx
3✔
684
        commitHash := commitTxBroadcast.TxHash()
685

3✔
686
        // Check whether our latest local state hit the chain.
3✔
UNCOV
687
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
×
UNCOV
688
                return false, nil
×
UNCOV
689
        }
×
690

691
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
692
        if err := c.dispatchLocalForceClose(
693
                commitSpend, broadcastStateNum, chainSet.commitSet,
3✔
694
        ); err != nil {
3✔
695
                return false, fmt.Errorf("unable to handle local"+
696
                        "close for chan_point=%v: %v",
697
                        c.cfg.chanState.FundingOutpoint, err)
698
        }
699

700
        return true, nil
701
}
702

703
// handleKnownRemoteState checks whether the passed spend is a remote state
704
// that is known to us (a revoked, current or pending state). If so we will act
3✔
705
// on this state using the passed chainSet. If this is not a known remote
3✔
706
// state, false is returned.
3✔
707
func (c *chainWatcher) handleKnownRemoteState(
3✔
708
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
6✔
709
        chainSet *chainSet) (bool, error) {
3✔
710

3✔
711
        // If the channel is recovered, we won't have any remote commit to
712
        // check against, so imemdiately return.
3✔
713
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
3✔
714
                return false, nil
3✔
715
        }
3✔
716

6✔
717
        commitTxBroadcast := commitSpend.SpendingTx
3✔
718
        commitHash := commitTxBroadcast.TxHash()
3✔
719

720
        switch {
3✔
721
        // If the spending transaction matches the current latest state, then
3✔
722
        // they've initiated a unilateral close. So we'll trigger the
3✔
723
        // unilateral close signal so subscribers can clean up the state as
3✔
UNCOV
724
        // necessary.
×
UNCOV
725
        case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
×
UNCOV
726
                log.Infof("Remote party broadcast base set, "+
×
UNCOV
727
                        "commit_num=%v", chainSet.remoteStateNum)
×
728

729
                chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
3✔
730
                err := c.dispatchRemoteForceClose(
731
                        commitSpend, chainSet.remoteCommit,
732
                        chainSet.commitSet,
733
                        c.cfg.chanState.RemoteCurrentRevocation,
734
                )
735
                if err != nil {
736
                        return false, fmt.Errorf("unable to handle remote "+
737
                                "close for chan_point=%v: %v",
738
                                c.cfg.chanState.FundingOutpoint, err)
3✔
739
                }
3✔
740

3✔
741
                return true, nil
3✔
742

6✔
743
        // We'll also handle the case of the remote party broadcasting
3✔
744
        // their commitment transaction which is one height above ours.
3✔
745
        // This case can arise when we initiate a state transition, but
746
        // the remote party has a fail crash _after_ accepting the new
3✔
747
        // state, but _before_ sending their signature to us.
3✔
748
        case chainSet.remotePendingCommit != nil &&
3✔
749
                chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
3✔
750

751
                log.Infof("Remote party broadcast pending set, "+
752
                        "commit_num=%v", chainSet.remoteStateNum+1)
753

754
                chainSet.commitSet.ConfCommitKey = fn.Some(RemotePendingHtlcSet)
3✔
755
                err := c.dispatchRemoteForceClose(
3✔
756
                        commitSpend, *chainSet.remotePendingCommit,
3✔
757
                        chainSet.commitSet,
3✔
758
                        c.cfg.chanState.RemoteNextRevocation,
3✔
759
                )
3✔
760
                if err != nil {
3✔
761
                        return false, fmt.Errorf("unable to handle remote "+
3✔
762
                                "close for chan_point=%v: %v",
3✔
763
                                c.cfg.chanState.FundingOutpoint, err)
3✔
764
                }
3✔
UNCOV
765

×
UNCOV
766
                return true, nil
×
UNCOV
767
        }
×
UNCOV
768

×
769
        // This is neither a remote force close or a "future" commitment, we
770
        // now check whether it's a remote breach and properly handle it.
3✔
771
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
772
}
773

774
// handlePossibleBreach checks whether the remote has breached and dispatches a
775
// breach resolution to claim funds.
776
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
777
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
UNCOV
778

×
UNCOV
779
        // We check if we have a revoked state at this state num that matches
×
UNCOV
780
        // the spend transaction.
×
UNCOV
781
        spendHeight := uint32(commitSpend.SpendingHeight)
×
UNCOV
782
        retribution, err := lnwallet.NewBreachRetribution(
×
UNCOV
783
                c.cfg.chanState, broadcastStateNum, spendHeight,
×
UNCOV
784
                commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
×
UNCOV
785
        )
×
UNCOV
786

×
UNCOV
787
        switch {
×
UNCOV
788
        // If we had no log entry at this height, this was not a revoked state.
×
UNCOV
789
        case err == channeldb.ErrLogEntryNotFound:
×
UNCOV
790
                return false, nil
×
UNCOV
791
        case err == channeldb.ErrNoPastDeltas:
×
UNCOV
792
                return false, nil
×
UNCOV
793

×
794
        case err != nil:
795
                return false, fmt.Errorf("unable to create breach "+
×
796
                        "retribution: %v", err)
797
        }
798

799
        // We found a revoked state at this height, but it could still be our
800
        // own broadcasted state we are looking at. Therefore check that the
3✔
801
        // commit matches before assuming it was a breach.
802
        commitHash := commitSpend.SpendingTx.TxHash()
803
        if retribution.BreachTxHash != commitHash {
804
                return false, nil
805
        }
806

3✔
807
        // Create an AnchorResolution for the breached state.
3✔
808
        anchorRes, err := lnwallet.NewAnchorResolution(
3✔
809
                c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
3✔
810
                lntypes.Remote,
3✔
811
        )
3✔
812
        if err != nil {
3✔
813
                return false, fmt.Errorf("unable to create anchor "+
3✔
814
                        "resolution: %v", err)
3✔
815
        }
3✔
816

3✔
817
        // We'll set the ConfCommitKey here as the remote htlc set. This is
818
        // only used to ensure a nil-pointer-dereference doesn't occur and is
3✔
819
        // not used otherwise. The HTLC's may not exist for the
3✔
820
        // RemotePendingHtlcSet.
3✔
821
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
3✔
822

UNCOV
823
        // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
×
UNCOV
824
        // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
×
UNCOV
825
        // broadcast to allow subscribers to swiftly dispatch justice!!!
×
826
        err = c.dispatchContractBreach(
827
                commitSpend, chainSet, broadcastStateNum, retribution,
828
                anchorRes,
829
        )
830
        if err != nil {
831
                return false, fmt.Errorf("unable to handle channel "+
3✔
832
                        "breach for chan_point=%v: %v",
3✔
833
                        c.cfg.chanState.FundingOutpoint, err)
×
834
        }
×
835

836
        return true, nil
837
}
3✔
838

3✔
839
// handleUnknownRemoteState is the last attempt we make at reclaiming funds
3✔
840
// from the closed channel, by checkin whether the passed spend _could_ be a
3✔
841
// remote spend that is unknown to us (we lost state). We will try to initiate
3✔
UNCOV
842
// Data Loss Protection in order to restore our commit point and reclaim our
×
UNCOV
843
// funds from the channel. If we are not able to act on it, false is returned.
×
UNCOV
844
func (c *chainWatcher) handleUnknownRemoteState(
×
845
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
846
        chainSet *chainSet) (bool, error) {
847

848
        log.Warnf("Remote node broadcast state #%v, "+
849
                "which is more than 1 beyond best known "+
850
                "state #%v!!! Attempting recovery...",
3✔
851
                broadcastStateNum, chainSet.remoteStateNum)
3✔
852

3✔
853
        // If this isn't a tweakless commitment, then we'll need to wait for
3✔
854
        // the remote party's latest unrevoked commitment point to be presented
3✔
855
        // to us as we need this to sweep. Otherwise, we can dispatch the
3✔
856
        // remote close and sweep immediately using a fake commitPoint as it
3✔
857
        // isn't actually needed for recovery anymore.
3✔
858
        commitPoint := c.cfg.chanState.RemoteCurrentRevocation
3✔
859
        tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
3✔
UNCOV
860
        if !tweaklessCommit {
×
UNCOV
861
                commitPoint = c.waitForCommitmentPoint()
×
UNCOV
862
                if commitPoint == nil {
×
863
                        return false, fmt.Errorf("unable to get commit point")
×
864
                }
865

3✔
866
                log.Infof("Recovered commit point(%x) for "+
867
                        "channel(%v)! Now attempting to use it to "+
868
                        "sweep our funds...",
869
                        commitPoint.SerializeCompressed(),
870
                        c.cfg.chanState.FundingOutpoint)
871
        } else {
872
                log.Infof("ChannelPoint(%v) is tweakless, "+
873
                        "moving to sweep directly on chain",
874
                        c.cfg.chanState.FundingOutpoint)
875
        }
3✔
876

3✔
877
        // Since we don't have the commitment stored for this state, we'll just
3✔
878
        // pass an empty commitment within the commitment set. Note that this
3✔
879
        // means we won't be able to recover any HTLC funds.
3✔
880
        //
3✔
881
        // TODO(halseth): can we try to recover some HTLCs?
3✔
882
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
3✔
883
        err := c.dispatchRemoteForceClose(
3✔
884
                commitSpend, channeldb.ChannelCommitment{},
3✔
885
                chainSet.commitSet, commitPoint,
3✔
886
        )
3✔
887
        if err != nil {
3✔
888
                return false, fmt.Errorf("unable to handle remote "+
3✔
889
                        "close for chan_point=%v: %v",
3✔
890
                        c.cfg.chanState.FundingOutpoint, err)
×
891
        }
×
UNCOV
892

×
UNCOV
893
        return true, nil
×
894
}
UNCOV
895

×
UNCOV
896
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
×
UNCOV
897
// to a script that the wallet controls or the channel defines as its delivery
×
UNCOV
898
// script . If no outputs pay to us (determined by these criteria), then we
×
UNCOV
899
// return zero. This is possible as our output may have been trimmed due to
×
900
// being dust.
3✔
901
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
3✔
902
        // There are two main cases we have to handle here. First, in the coop
3✔
903
        // close case we will always have saved the delivery address we used
3✔
904
        // whether it was from the upfront shutdown, from the delivery address
3✔
905
        // requested at close time, or even an automatically generated one. All
906
        // coop-close cases can be identified in the following manner:
907
        shutdown, _ := c.cfg.chanState.ShutdownInfo()
908
        oDeliveryAddr := fn.MapOption(
909
                func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
910
                        return i.DeliveryScript.Val
911
                })(shutdown)
3✔
912

3✔
913
        // Here we define a function capable of identifying whether an output
3✔
914
        // corresponds with our local delivery script from a ShutdownInfo if we
3✔
915
        // have a ShutdownInfo for this chainWatcher's underlying channel.
3✔
916
        //
3✔
UNCOV
917
        // isDeliveryOutput :: *TxOut -> bool
×
UNCOV
918
        isDeliveryOutput := func(o *wire.TxOut) bool {
×
UNCOV
919
                return fn.ElimOption(
×
UNCOV
920
                        oDeliveryAddr,
×
921
                        // If we don't have a delivery addr, then the output
922
                        // can't match it.
3✔
923
                        func() bool { return false },
924
                        // Otherwise if the PkScript of the TxOut matches our
925
                        // delivery script then this is a delivery output.
926
                        func(a lnwire.DeliveryAddress) bool {
927
                                return slices.Equal(a, o.PkScript)
928
                        },
929
                )
930
        }
3✔
931

3✔
932
        // Here we define a function capable of identifying whether an output
3✔
933
        // belongs to the LND wallet. We use this as a heuristic in the case
3✔
934
        // where we might be looking for spendable force closure outputs.
3✔
935
        //
3✔
936
        // isWalletOutput :: *TxOut -> bool
3✔
937
        isWalletOutput := func(out *wire.TxOut) bool {
3✔
938
                _, addrs, _, err := txscript.ExtractPkScriptAddrs(
6✔
939
                        // Doesn't matter what net we actually pass in.
3✔
940
                        out.PkScript, &chaincfg.TestNet3Params,
3✔
941
                )
942
                if err != nil {
943
                        return false
944
                }
945

946
                return fn.Any(addrs, c.cfg.isOurAddr)
947
        }
6✔
948

3✔
949
        // Grab all of the outputs that correspond with our delivery address
3✔
950
        // or our wallet is aware of.
3✔
951
        outs := fn.Filter(tx.TxOut, fn.PredOr(isDeliveryOutput, isWalletOutput))
3✔
952

3✔
953
        // Grab the values for those outputs.
954
        vals := fn.Map(outs, func(o *wire.TxOut) int64 { return o.Value })
955

3✔
956
        // Return the sum.
3✔
957
        return btcutil.Amount(fn.Sum(vals))
3✔
958
}
959

960
// dispatchCooperativeClose processed a detect cooperative channel closure.
961
// We'll use the spending transaction to locate our output within the
962
// transaction, then clean up the database state. We'll also dispatch a
963
// notification to all subscribers that the channel has been closed in this
964
// manner.
965
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
966
        broadcastTx := commitSpend.SpendingTx
6✔
967

3✔
968
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
3✔
969
                c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
3✔
970

3✔
971
        // If the input *is* final, then we'll check to see which output is
3✔
UNCOV
972
        // ours.
×
UNCOV
973
        localAmt := c.toSelfAmount(broadcastTx)
×
974

975
        // Once this is known, we'll mark the state as fully closed in the
3✔
976
        // database. We can do this as a cooperatively closed channel has all
977
        // its outputs resolved after only one confirmation.
978
        closeSummary := &channeldb.ChannelCloseSummary{
979
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
980
                ChainHash:               c.cfg.chanState.ChainHash,
3✔
981
                ClosingTXID:             *commitSpend.SpenderTxHash,
3✔
982
                RemotePub:               c.cfg.chanState.IdentityPub,
3✔
983
                Capacity:                c.cfg.chanState.Capacity,
6✔
984
                CloseHeight:             uint32(commitSpend.SpendingHeight),
985
                SettledBalance:          localAmt,
986
                CloseType:               channeldb.CooperativeClose,
3✔
987
                ShortChanID:             c.cfg.chanState.ShortChanID(),
988
                IsPending:               true,
989
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
990
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
991
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
992
        }
993

994
        // Attempt to add a channel sync message to the close summary.
3✔
995
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
3✔
996
        if err != nil {
3✔
997
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
3✔
998
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
3✔
999
        } else {
3✔
1000
                closeSummary.LastChanSyncMsg = chanSync
3✔
1001
        }
3✔
1002

3✔
1003
        // Create a summary of all the information needed to handle the
3✔
1004
        // cooperative closure.
3✔
1005
        closeInfo := &CooperativeCloseInfo{
3✔
1006
                ChannelCloseSummary: closeSummary,
3✔
1007
        }
3✔
1008

3✔
1009
        // With the event processed, we'll now notify all subscribers of the
3✔
1010
        // event.
3✔
1011
        c.Lock()
3✔
1012
        for _, sub := range c.clientSubscriptions {
3✔
1013
                select {
3✔
1014
                case sub.CooperativeClosure <- closeInfo:
3✔
1015
                case <-c.quit:
3✔
1016
                        c.Unlock()
3✔
1017
                        return fmt.Errorf("exiting")
3✔
1018
                }
3✔
1019
        }
3✔
1020
        c.Unlock()
3✔
1021

3✔
1022
        return nil
3✔
1023
}
3✔
1024

3✔
1025
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
3✔
UNCOV
1026
func (c *chainWatcher) dispatchLocalForceClose(
×
UNCOV
1027
        commitSpend *chainntnfs.SpendDetail,
×
1028
        stateNum uint64, commitSet CommitSet) error {
3✔
1029

3✔
1030
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
3✔
1031
                "detected", c.cfg.chanState.FundingOutpoint)
1032

1033
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
1034
                c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
3✔
1035
                c.cfg.auxLeafStore, c.cfg.auxResolver,
3✔
1036
        )
3✔
1037
        if err != nil {
3✔
1038
                return err
3✔
1039
        }
3✔
1040

3✔
1041
        // As we've detected that the channel has been closed, immediately
6✔
1042
        // creating a close summary for future usage by related sub-systems.
3✔
1043
        chanSnapshot := forceClose.ChanSnapshot
3✔
UNCOV
1044
        closeSummary := &channeldb.ChannelCloseSummary{
×
UNCOV
1045
                ChanPoint:               chanSnapshot.ChannelPoint,
×
UNCOV
1046
                ChainHash:               chanSnapshot.ChainHash,
×
1047
                ClosingTXID:             forceClose.CloseTx.TxHash(),
1048
                RemotePub:               &chanSnapshot.RemoteIdentity,
1049
                Capacity:                chanSnapshot.Capacity,
3✔
1050
                CloseType:               channeldb.LocalForceClose,
3✔
1051
                IsPending:               true,
3✔
1052
                ShortChanID:             c.cfg.chanState.ShortChanID(),
1053
                CloseHeight:             uint32(commitSpend.SpendingHeight),
1054
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
1055
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
1056
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
1057
        }
3✔
1058

3✔
1059
        resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
3✔
1060
                fmt.Errorf("resolutions not found"),
3✔
1061
        )
3✔
1062
        if err != nil {
3✔
1063
                return err
3✔
1064
        }
3✔
1065

3✔
1066
        // If our commitment output isn't dust or we have active HTLC's on the
3✔
UNCOV
1067
        // commitment transaction, then we'll populate the balances on the
×
UNCOV
1068
        // close channel summary.
×
1069
        if resolutions.CommitResolution != nil {
1070
                localBalance := chanSnapshot.LocalBalance.ToSatoshis()
1071
                closeSummary.SettledBalance = localBalance
1072
                closeSummary.TimeLockedBalance = localBalance
3✔
1073
        }
3✔
1074

3✔
1075
        if resolutions.HtlcResolutions != nil {
3✔
1076
                for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
3✔
1077
                        htlcValue := btcutil.Amount(
3✔
1078
                                htlc.SweepSignDesc.Output.Value,
3✔
1079
                        )
3✔
1080
                        closeSummary.TimeLockedBalance += htlcValue
3✔
1081
                }
3✔
1082
        }
3✔
1083

3✔
1084
        // Attempt to add a channel sync message to the close summary.
3✔
1085
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
3✔
1086
        if err != nil {
3✔
1087
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
3✔
1088
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
3✔
1089
        } else {
3✔
1090
                closeSummary.LastChanSyncMsg = chanSync
3✔
1091
        }
3✔
UNCOV
1092

×
UNCOV
1093
        // With the event processed, we'll now notify all subscribers of the
×
1094
        // event.
1095
        closeInfo := &LocalUnilateralCloseInfo{
1096
                SpendDetail:            commitSpend,
1097
                LocalForceCloseSummary: forceClose,
1098
                ChannelCloseSummary:    closeSummary,
6✔
1099
                CommitSet:              commitSet,
3✔
1100
        }
3✔
1101
        c.Lock()
3✔
1102
        for _, sub := range c.clientSubscriptions {
3✔
1103
                select {
1104
                case sub.LocalUnilateralClosure <- closeInfo:
6✔
1105
                case <-c.quit:
6✔
1106
                        c.Unlock()
3✔
1107
                        return fmt.Errorf("exiting")
3✔
1108
                }
3✔
1109
        }
3✔
1110
        c.Unlock()
3✔
1111

1112
        return nil
1113
}
1114

3✔
1115
// dispatchRemoteForceClose processes a detected unilateral channel closure by
3✔
UNCOV
1116
// the remote party. This function will prepare a UnilateralCloseSummary which
×
UNCOV
1117
// will then be sent to any subscribers allowing them to resolve all our funds
×
1118
// in the channel on chain. Once this close summary is prepared, all registered
3✔
1119
// subscribers will receive a notification of this event. The commitPoint
3✔
1120
// argument should be set to the per_commitment_point corresponding to the
3✔
1121
// spending commitment.
1122
//
1123
// NOTE: The remoteCommit argument should be set to the stored commitment for
1124
// this particular state. If we don't have the commitment stored (should only
3✔
1125
// happen in case we have lost state) it should be set to an empty struct, in
3✔
1126
// which case we will attempt to sweep the non-HTLC output using the passed
3✔
1127
// commitPoint.
3✔
1128
func (c *chainWatcher) dispatchRemoteForceClose(
3✔
1129
        commitSpend *chainntnfs.SpendDetail,
3✔
1130
        remoteCommit channeldb.ChannelCommitment,
3✔
1131
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
6✔
1132

3✔
1133
        log.Infof("Unilateral close of ChannelPoint(%v) "+
3✔
UNCOV
1134
                "detected", c.cfg.chanState.FundingOutpoint)
×
UNCOV
1135

×
UNCOV
1136
        // First, we'll create a closure summary that contains all the
×
1137
        // materials required to let each subscriber sweep the funds in the
1138
        // channel on-chain.
1139
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
3✔
1140
                c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
3✔
1141
                commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
3✔
1142
        )
1143
        if err != nil {
1144
                return err
1145
        }
1146

1147
        // With the event processed, we'll now notify all subscribers of the
1148
        // event.
1149
        c.Lock()
1150
        for _, sub := range c.clientSubscriptions {
1151
                select {
1152
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
1153
                        UnilateralCloseSummary: uniClose,
1154
                        CommitSet:              commitSet,
1155
                }:
1156
                case <-c.quit:
1157
                        c.Unlock()
1158
                        return fmt.Errorf("exiting")
1159
                }
1160
        }
3✔
1161
        c.Unlock()
3✔
1162

3✔
1163
        return nil
3✔
1164
}
3✔
1165

3✔
1166
// dispatchContractBreach processes a detected contract breached by the remote
3✔
1167
// party. This method is to be called once we detect that the remote party has
3✔
1168
// broadcast a prior revoked commitment state. This method well prepare all the
3✔
1169
// materials required to bring the cheater to justice, then notify all
3✔
1170
// registered subscribers of this event.
3✔
1171
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
3✔
1172
        chainSet *chainSet, broadcastStateNum uint64,
3✔
UNCOV
1173
        retribution *lnwallet.BreachRetribution,
×
UNCOV
1174
        anchorRes *lnwallet.AnchorResolution) error {
×
1175

1176
        log.Warnf("Remote peer has breached the channel contract for "+
1177
                "ChannelPoint(%v). Revoked state #%v was broadcast!!!",
1178
                c.cfg.chanState.FundingOutpoint, broadcastStateNum)
3✔
1179

6✔
1180
        if err := c.cfg.chanState.MarkBorked(); err != nil {
3✔
1181
                return fmt.Errorf("unable to mark channel as borked: %w", err)
1182
        }
1183

1184
        spendHeight := uint32(spendEvent.SpendingHeight)
3✔
UNCOV
1185

×
UNCOV
1186
        log.Debugf("Punishment breach retribution created: %v",
×
UNCOV
1187
                lnutils.NewLogClosure(func() string {
×
1188
                        retribution.KeyRing.LocalHtlcKey = nil
1189
                        retribution.KeyRing.RemoteHtlcKey = nil
1190
                        retribution.KeyRing.ToLocalKey = nil
3✔
1191
                        retribution.KeyRing.ToRemoteKey = nil
3✔
1192
                        retribution.KeyRing.RevocationKey = nil
3✔
1193
                        return spew.Sdump(retribution)
1194
                }))
1195

1196
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
1197
        closeSummary := channeldb.ChannelCloseSummary{
1198
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
1199
                ChainHash:               c.cfg.chanState.ChainHash,
1200
                ClosingTXID:             *spendEvent.SpenderTxHash,
1201
                CloseHeight:             spendHeight,
1202
                RemotePub:               c.cfg.chanState.IdentityPub,
1203
                Capacity:                c.cfg.chanState.Capacity,
3✔
1204
                SettledBalance:          settledBalance,
3✔
1205
                CloseType:               channeldb.BreachClose,
3✔
1206
                IsPending:               true,
3✔
1207
                ShortChanID:             c.cfg.chanState.ShortChanID(),
3✔
1208
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
3✔
1209
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
3✔
UNCOV
1210
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
UNCOV
1211
        }
×
1212

1213
        // Attempt to add a channel sync message to the close summary.
3✔
1214
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
3✔
1215
        if err != nil {
3✔
1216
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
6✔
1217
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
3✔
1218
        } else {
3✔
1219
                closeSummary.LastChanSyncMsg = chanSync
3✔
1220
        }
3✔
1221

3✔
1222
        // Hand the retribution info over to the BreachArbitrator. This function
3✔
1223
        // will wait for a response from the breach arbiter and then proceed to
3✔
1224
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1225
        // will then mark the channel as closed after resolutions and the
3✔
1226
        // commit set are logged in the arbitrator log.
3✔
1227
        if err := c.cfg.contractBreach(retribution); err != nil {
3✔
1228
                log.Errorf("unable to hand breached contract off to "+
3✔
1229
                        "BreachArbitrator: %v", err)
3✔
1230
                return err
3✔
1231
        }
3✔
1232

3✔
1233
        breachRes := &BreachResolution{
3✔
1234
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
3✔
1235
        }
3✔
1236

3✔
1237
        breachInfo := &BreachCloseInfo{
3✔
1238
                CommitHash:       spendEvent.SpendingTx.TxHash(),
3✔
1239
                BreachResolution: breachRes,
3✔
1240
                AnchorResolution: anchorRes,
3✔
1241
                CommitSet:        chainSet.commitSet,
3✔
1242
                CloseSummary:     closeSummary,
3✔
1243
        }
3✔
1244

3✔
UNCOV
1245
        // With the event processed and channel closed, we'll now notify all
×
UNCOV
1246
        // subscribers of the event.
×
1247
        c.Lock()
3✔
1248
        for _, sub := range c.clientSubscriptions {
3✔
1249
                select {
3✔
1250
                case sub.ContractBreach <- breachInfo:
1251
                case <-c.quit:
1252
                        c.Unlock()
1253
                        return fmt.Errorf("quitting")
1254
                }
1255
        }
1256
        c.Unlock()
3✔
UNCOV
1257

×
UNCOV
1258
        return nil
×
UNCOV
1259
}
×
UNCOV
1260

×
1261
// waitForCommitmentPoint waits for the commitment point to be inserted into
1262
// the local database. We'll use this method in the DLP case, to wait for the
3✔
1263
// remote party to send us their point, as we can't proceed until we have that.
3✔
1264
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
3✔
1265
        // If we are lucky, the remote peer sent us the correct commitment
3✔
1266
        // point during channel sync, such that we can sweep our funds. If we
3✔
1267
        // cannot find the commit point, there's not much we can do other than
3✔
1268
        // wait for us to retrieve it. We will attempt to retrieve it from the
3✔
1269
        // peer each time we connect to it.
3✔
1270
        //
3✔
1271
        // TODO(halseth): actively initiate re-connection to the peer?
3✔
1272
        backoff := minCommitPointPollTimeout
3✔
1273
        for {
3✔
1274
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
3✔
1275
                if err == nil {
3✔
1276
                        return commitPoint
3✔
1277
                }
6✔
1278

3✔
1279
                log.Errorf("Unable to retrieve commitment point for "+
3✔
1280
                        "channel(%v) with lost state: %v. Retrying in %v.",
×
1281
                        c.cfg.chanState.FundingOutpoint, err, backoff)
×
1282

×
1283
                select {
1284
                // Wait before retrying, with an exponential backoff.
1285
                case <-time.After(backoff):
3✔
1286
                        backoff = 2 * backoff
3✔
1287
                        if backoff > maxCommitPointPollTimeout {
3✔
1288
                                backoff = maxCommitPointPollTimeout
1289
                        }
1290

1291
                case <-c.quit:
1292
                        return nil
UNCOV
1293
                }
×
UNCOV
1294
        }
×
UNCOV
1295
}
×
UNCOV
1296

×
UNCOV
1297
// deriveFundingPkScript derives the script used in the funding output.
×
UNCOV
1298
func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) {
×
UNCOV
1299
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
×
UNCOV
1300
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
×
UNCOV
1301

×
UNCOV
1302
        var (
×
UNCOV
1303
                err             error
×
UNCOV
1304
                fundingPkScript []byte
×
UNCOV
1305
        )
×
UNCOV
1306

×
1307
        if chanState.ChanType.IsTaproot() {
UNCOV
1308
                fundingPkScript, _, err = input.GenTaprootFundingScript(
×
UNCOV
1309
                        localKey, remoteKey, 0, chanState.TapscriptRoot,
×
UNCOV
1310
                )
×
UNCOV
1311
                if err != nil {
×
1312
                        return nil, err
×
1313
                }
UNCOV
1314
        } else {
×
UNCOV
1315
                multiSigScript, err := input.GenMultiSigScript(
×
UNCOV
1316
                        localKey.SerializeCompressed(),
×
UNCOV
1317
                        remoteKey.SerializeCompressed(),
×
UNCOV
1318
                )
×
1319
                if err != nil {
1320
                        return nil, err
×
1321
                }
×
1322
                fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
1323
                if err != nil {
1324
                        return nil, err
1325
                }
1326
        }
1327

3✔
1328
        return fundingPkScript, nil
3✔
1329
}
3✔
1330

3✔
1331
// handleCommitSpend takes a spending tx of the funding output and handles the
3✔
1332
// channel close based on the closure type.
3✔
1333
func (c *chainWatcher) handleCommitSpend(
3✔
1334
        commitSpend *chainntnfs.SpendDetail) error {
3✔
1335

3✔
1336
        commitTxBroadcast := commitSpend.SpendingTx
6✔
1337

3✔
1338
        // First, we'll construct the chainset which includes all the data we
3✔
1339
        // need to dispatch an event to our subscribers about this possible
3✔
1340
        // channel close event.
3✔
UNCOV
1341
        chainSet, err := newChainSet(c.cfg.chanState)
×
UNCOV
1342
        if err != nil {
×
1343
                return fmt.Errorf("create commit set: %w", err)
3✔
1344
        }
3✔
1345

3✔
1346
        // Decode the state hint encoded within the commitment transaction to
3✔
1347
        // determine if this is a revoked state or not.
3✔
1348
        obfuscator := c.stateHintObfuscator
3✔
UNCOV
1349
        broadcastStateNum := c.cfg.extractStateNumHint(
×
UNCOV
1350
                commitTxBroadcast, obfuscator,
×
1351
        )
3✔
1352

3✔
UNCOV
1353
        // We'll go on to check whether it could be our own commitment that was
×
UNCOV
1354
        // published and know is confirmed.
×
1355
        ok, err := c.handleKnownLocalState(
1356
                commitSpend, broadcastStateNum, chainSet,
1357
        )
3✔
1358
        if err != nil {
1359
                return fmt.Errorf("handle known local state: %w", err)
1360
        }
1361
        if ok {
1362
                return nil
1363
        }
3✔
1364

3✔
1365
        // Now that we know it is neither a non-cooperative closure nor a local
3✔
1366
        // close with the latest state, we check if it is the remote that
3✔
1367
        // closed with any prior or current state.
3✔
1368
        ok, err = c.handleKnownRemoteState(
3✔
1369
                commitSpend, broadcastStateNum, chainSet,
3✔
1370
        )
3✔
1371
        if err != nil {
3✔
1372
                return fmt.Errorf("handle known remote state: %w", err)
×
1373
        }
×
1374
        if ok {
1375
                return nil
1376
        }
1377

3✔
1378
        // Next, we'll check to see if this is a cooperative channel closure or
3✔
1379
        // not. This is characterized by having an input sequence number that's
3✔
1380
        // finalized. This won't happen with regular commitment transactions
3✔
1381
        // due to the state hint encoding scheme.
3✔
1382
        switch commitTxBroadcast.TxIn[0].Sequence {
3✔
1383
        case wire.MaxTxInSequenceNum:
3✔
1384
                fallthrough
3✔
1385
        case mempool.MaxRBFSequence:
3✔
1386
                // TODO(roasbeef): rare but possible, need itest case for
3✔
1387
                err := c.dispatchCooperativeClose(commitSpend)
3✔
UNCOV
1388
                if err != nil {
×
1389
                        return fmt.Errorf("handle coop close: %w", err)
×
1390
                }
6✔
1391

3✔
1392
                return nil
3✔
1393
        }
1394

1395
        log.Warnf("Unknown commitment broadcast for ChannelPoint(%v) ",
1396
                c.cfg.chanState.FundingOutpoint)
1397

3✔
1398
        // We'll try to recover as best as possible from losing state.  We
3✔
1399
        // first check if this was a local unknown state. This could happen if
3✔
1400
        // we force close, then lose state or attempt recovery before the
3✔
UNCOV
1401
        // commitment confirms.
×
UNCOV
1402
        ok, err = c.handleUnknownLocalState(
×
1403
                commitSpend, broadcastStateNum, chainSet,
6✔
1404
        )
3✔
1405
        if err != nil {
3✔
1406
                return fmt.Errorf("handle known local state: %w", err)
1407
        }
1408
        if ok {
1409
                return nil
1410
        }
1411

3✔
1412
        // Since it was neither a known remote state, nor a local state that
3✔
1413
        // was published, it most likely mean we lost state and the remote node
3✔
1414
        // closed. In this case we must start the DLP protocol in hope of
3✔
1415
        // getting our money back.
3✔
1416
        ok, err = c.handleUnknownRemoteState(
3✔
1417
                commitSpend, broadcastStateNum, chainSet,
3✔
UNCOV
1418
        )
×
UNCOV
1419
        if err != nil {
×
1420
                return fmt.Errorf("handle unknown remote state: %w", err)
1421
        }
3✔
1422
        if ok {
1423
                return nil
1424
        }
3✔
1425

3✔
1426
        log.Errorf("Unable to handle spending tx %v of channel point %v",
3✔
1427
                commitTxBroadcast.TxHash(), c.cfg.chanState.FundingOutpoint)
3✔
1428

3✔
1429
        return nil
3✔
1430
}
3✔
1431

3✔
1432
// checkFundingSpend performs a non-blocking read on the spendNtfn channel to
3✔
1433
// check whether there's a commit spend already. Returns the spend details if
3✔
1434
// found.
3✔
UNCOV
1435
func (c *chainWatcher) checkFundingSpend() *chainntnfs.SpendDetail {
×
UNCOV
1436
        select {
×
1437
        // We've detected a spend of the channel onchain! Depending on the type
6✔
1438
        // of spend, we'll act accordingly, so we'll examine the spending
3✔
1439
        // transaction to determine what we should do.
3✔
1440
        //
1441
        // TODO(Roasbeef): need to be able to ensure this only triggers
1442
        // on confirmation, to ensure if multiple txns are broadcast, we
1443
        // act on the one that's timestamped
1444
        case spend, ok := <-c.fundingSpendNtfn.Spend:
1445
                // If the channel was closed, then this means that the notifier
3✔
1446
                // exited, so we will as well.
3✔
1447
                if !ok {
3✔
1448
                        return nil
3✔
1449
                }
×
UNCOV
1450

×
1451
                log.Debugf("Found spend details for funding output: %v",
6✔
1452
                        spend.SpenderTxHash)
3✔
1453

3✔
1454
                return spend
UNCOV
1455

×
UNCOV
1456
        default:
×
UNCOV
1457
        }
×
UNCOV
1458

×
1459
        return nil
1460
}
1461

1462
// chanPointConfirmed checks whether the given channel point has confirmed.
1463
// This is used to ensure that the funding output has confirmed on chain before
1464
// we proceed with the rest of the close observer logic for taproot channels.
3✔
1465
// Check the docs in `fundingConfirmedNtfn` for details.
3✔
1466
func (c *chainWatcher) chanPointConfirmed() bool {
1467
        op := c.cfg.chanState.FundingOutpoint
1468

1469
        select {
1470
        case _, ok := <-c.fundingConfirmedNtfn.Confirmed:
1471
                // If the channel was closed, then this means that the notifier
1472
                // exited, so we will as well.
UNCOV
1473
                if !ok {
×
UNCOV
1474
                        return false
×
UNCOV
1475
                }
×
UNCOV
1476

×
UNCOV
1477
                log.Debugf("Taproot ChannelPoint(%v) confirmed", op)
×
UNCOV
1478

×
1479
                // The channel point has confirmed on chain. We now cancel the
UNCOV
1480
                // subscription.
×
UNCOV
1481
                c.fundingConfirmedNtfn.Cancel()
×
UNCOV
1482

×
UNCOV
1483
                return true
×
1484

1485
        default:
3✔
1486
                log.Infof("Taproot ChannelPoint(%v) not confirmed yet", op)
1487

1488
                return false
3✔
1489
        }
1490
}
1491

1492
// handleBlockbeat takes a blockbeat and queries for a spending tx for the
1493
// funding output. If the spending tx is found, it will be handled based on the
1494
// closure type.
1495
func (c *chainWatcher) handleBlockbeat(beat chainio.Blockbeat) {
3✔
1496
        // Notify the chain watcher has processed the block.
3✔
1497
        defer c.NotifyBlockProcessed(beat, nil)
3✔
1498

3✔
1499
        // If we have a fundingConfirmedNtfn, it means this is a taproot
3✔
1500
        // channel that is pending, before we proceed, we want to ensure that
3✔
1501
        // the expected funding output has confirmed on chain. Check the docs
3✔
1502
        // in `fundingConfirmedNtfn` for details.
6✔
1503
        if c.fundingConfirmedNtfn != nil {
3✔
1504
                // If the funding output hasn't confirmed in this block, we
3✔
1505
                // will check it again in the next block.
1506
                if !c.chanPointConfirmed() {
3✔
1507
                        return
3✔
1508
                }
3✔
1509
        }
3✔
1510

3✔
1511
        // Perform a non-blocking read to check whether the funding output was
3✔
1512
        // spent.
3✔
1513
        spend := c.checkFundingSpend()
1514
        if spend == nil {
3✔
1515
                log.Tracef("No spend found for ChannelPoint(%v) in block %v",
3✔
1516
                        c.cfg.chanState.FundingOutpoint, beat.Height())
3✔
1517

3✔
1518
                return
1519
        }
1520

1521
        // The funding output was spent, we now handle it by sending a close
1522
        // event to the channel arbitrator.
1523
        err := c.handleCommitSpend(spend)
1524
        if err != nil {
3✔
1525
                log.Errorf("Failed to handle commit spend: %v", err)
3✔
1526
        }
3✔
1527
}
3✔
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