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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

56.74
/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 {
16✔
109
        if c == nil {
16✔
110
                return true
×
111
        }
×
112

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

119
        return true
8✔
120
}
121

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

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

131
        return htlcSets
14✔
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
136
// possible on-chain events: a cooperative channel closure, a unilateral
137
// channel closure, and a channel breach. The fourth type: a force close is
138
// locally initiated, so we don't provide any event stream for said event.
139
type ChainEventSubscription struct {
140
        // ChanPoint is that channel that chain events will be dispatched for.
141
        ChanPoint wire.OutPoint
142

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

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

151
        // CooperativeClosure is a signal that will be sent upon once a
152
        // cooperative channel closure has been detected confirmed.
153
        CooperativeClosure chan *CooperativeCloseInfo
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
157
        // material required to bring the cheating channel peer to justice.
158
        ContractBreach chan *BreachCloseInfo
159

160
        // Cancel cancels the subscription to the event stream for a particular
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) {
26✔
259
        // In order to be able to detect the nature of a potential channel
26✔
260
        // closure we'll need to reconstruct the state hint bytes used to
26✔
261
        // obfuscate the commitment state number encoded in the lock time and
26✔
262
        // sequence fields.
26✔
263
        var stateHint [lnwallet.StateHintSize]byte
26✔
264
        chanState := cfg.chanState
26✔
265
        if chanState.IsInitiator {
52✔
266
                stateHint = lnwallet.DeriveStateHintObfuscator(
26✔
267
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
26✔
268
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
26✔
269
                )
26✔
270
        } else {
26✔
UNCOV
271
                stateHint = lnwallet.DeriveStateHintObfuscator(
×
UNCOV
272
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
×
UNCOV
273
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
×
UNCOV
274
                )
×
UNCOV
275
        }
×
276

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

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

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

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

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

UNCOV
315
                c.fundingConfirmedNtfn = confNtfn
×
316
        }
317

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

26✔
321
        return c, nil
26✔
322
}
323

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

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

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

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

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

26✔
347
        return nil
26✔
348
}
349

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

356
        close(c.quit)
26✔
357

26✔
358
        c.wg.Wait()
26✔
359

26✔
360
        return nil
26✔
361
}
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
366
// watcher to regain those committed resources.
367
func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
26✔
368

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

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

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

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

26✔
394
        return sub
26✔
395
}
396

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

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

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

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

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

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

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

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

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.
488
        ourCommit := false
11✔
489
        for _, output := range commitSpend.SpendingTx.TxOut {
27✔
490
                pkScript := output.PkScript
16✔
491

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

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

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

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

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

521
        return true, nil
7✔
522
}
523

524
// chainSet includes all the information we need to dispatch a channel close
525
// event to any subscribers.
526
type chainSet struct {
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
532

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

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

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

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

549
// newChainSet creates a new chainSet given the current up to date channel
550
// state.
551
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
15✔
552
        // First, we'll grab the current unrevoked commitments for ourselves
15✔
553
        // and the remote party.
15✔
554
        localCommit, remoteCommit, err := chanState.LatestCommitments()
15✔
555
        if err != nil {
15✔
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",
15✔
561
                chanState.FundingOutpoint, chanState.ChanType,
15✔
562
                spew.Sdump(localCommit))
15✔
563
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
15✔
564
                chanState.FundingOutpoint, chanState.ChanType,
15✔
565
                spew.Sdump(remoteCommit))
15✔
566

15✔
567
        // Fetch the current known commit height for the remote party, and
15✔
568
        // their pending commitment chain tip if it exists.
15✔
569
        remoteStateNum := remoteCommit.CommitHeight
15✔
570
        remoteChainTip, err := chanState.RemoteCommitChainTip()
15✔
571
        if err != nil && err != channeldb.ErrNoPendingCommit {
15✔
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{
15✔
581
                HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
15✔
582
                        LocalHtlcSet:  localCommit.Htlcs,
15✔
583
                        RemoteHtlcSet: remoteCommit.Htlcs,
15✔
584
                },
15✔
585
        }
15✔
586

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

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

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

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

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

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

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

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

642
                // If the funding outpoint is spent, we now go ahead and handle
643
                // it. Note that we cannot rely solely on the `block` event
644
                // above to trigger a close event, as deep down, the receiving
645
                // of block notifications and the receiving of spending
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:
15✔
650
                        // If the channel was closed, then this means that the
15✔
651
                        // notifier exited, so we will as well.
15✔
652
                        if !ok {
15✔
UNCOV
653
                                return
×
UNCOV
654
                        }
×
655

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

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

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) {
15✔
676

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

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

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

691
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
2✔
692
        if err := c.dispatchLocalForceClose(
2✔
693
                commitSpend, broadcastStateNum, chainSet.commitSet,
2✔
694
        ); err != nil {
2✔
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
2✔
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
705
// on this state using the passed chainSet. If this is not a known remote
706
// state, false is returned.
707
func (c *chainWatcher) handleKnownRemoteState(
708
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
709
        chainSet *chainSet) (bool, error) {
13✔
710

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

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

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

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

741
                return true, nil
1✔
742

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

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

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

766
                return true, nil
1✔
767
        }
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.
771
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
11✔
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) {
11✔
778

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

11✔
787
        switch {
11✔
788
        // If we had no log entry at this height, this was not a revoked state.
789
        case err == channeldb.ErrLogEntryNotFound:
8✔
790
                return false, nil
8✔
791
        case err == channeldb.ErrNoPastDeltas:
3✔
792
                return false, nil
3✔
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
801
        // commit matches before assuming it was a breach.
UNCOV
802
        commitHash := commitSpend.SpendingTx.TxHash()
×
UNCOV
803
        if retribution.BreachTxHash != commitHash {
×
804
                return false, nil
×
805
        }
×
806

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

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
819
        // not used otherwise. The HTLC's may not exist for the
820
        // RemotePendingHtlcSet.
UNCOV
821
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
×
UNCOV
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!!!
×
UNCOV
826
        err = c.dispatchContractBreach(
×
UNCOV
827
                commitSpend, chainSet, broadcastStateNum, retribution,
×
UNCOV
828
                anchorRes,
×
UNCOV
829
        )
×
UNCOV
830
        if err != nil {
×
831
                return false, fmt.Errorf("unable to handle channel "+
×
832
                        "breach for chan_point=%v: %v",
×
833
                        c.cfg.chanState.FundingOutpoint, err)
×
834
        }
×
835

UNCOV
836
        return true, nil
×
837
}
838

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

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

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

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

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

893
        return true, nil
4✔
894
}
895

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

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

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

UNCOV
946
                return fn.Any(addrs, c.cfg.isOurAddr)
×
947
        }
948

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

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

956
        // Return the sum.
UNCOV
957
        return btcutil.Amount(fn.Sum(vals))
×
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.
UNCOV
965
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
×
UNCOV
966
        broadcastTx := commitSpend.SpendingTx
×
UNCOV
967

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

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

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

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

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

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

×
UNCOV
1022
        return nil
×
1023
}
1024

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

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

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

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

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

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

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

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

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

9✔
1112
        return nil
9✔
1113
}
1114

1115
// dispatchRemoteForceClose processes a detected unilateral channel closure by
1116
// the remote party. This function will prepare a UnilateralCloseSummary which
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
1119
// subscribers will receive a notification of this event. The commitPoint
1120
// argument should be set to the per_commitment_point corresponding to the
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
1125
// happen in case we have lost state) it should be set to an empty struct, in
1126
// which case we will attempt to sweep the non-HTLC output using the passed
1127
// commitPoint.
1128
func (c *chainWatcher) dispatchRemoteForceClose(
1129
        commitSpend *chainntnfs.SpendDetail,
1130
        remoteCommit channeldb.ChannelCommitment,
1131
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
6✔
1132

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

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

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

6✔
1163
        return nil
6✔
1164
}
1165

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

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

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

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

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

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

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

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

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

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

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

×
UNCOV
1258
        return nil
×
1259
}
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
1263
// remote party to send us their point, as we can't proceed until we have that.
1264
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
4✔
1265
        // If we are lucky, the remote peer sent us the correct commitment
4✔
1266
        // point during channel sync, such that we can sweep our funds. If we
4✔
1267
        // cannot find the commit point, there's not much we can do other than
4✔
1268
        // wait for us to retrieve it. We will attempt to retrieve it from the
4✔
1269
        // peer each time we connect to it.
4✔
1270
        //
4✔
1271
        // TODO(halseth): actively initiate re-connection to the peer?
4✔
1272
        backoff := minCommitPointPollTimeout
4✔
1273
        for {
8✔
1274
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
4✔
1275
                if err == nil {
8✔
1276
                        return commitPoint
4✔
1277
                }
4✔
1278

1279
                log.Errorf("Unable to retrieve commitment point for "+
×
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):
×
1286
                        backoff = 2 * backoff
×
1287
                        if backoff > maxCommitPointPollTimeout {
×
1288
                                backoff = maxCommitPointPollTimeout
×
1289
                        }
×
1290

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

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

26✔
1302
        var (
26✔
1303
                err             error
26✔
1304
                fundingPkScript []byte
26✔
1305
        )
26✔
1306

26✔
1307
        if chanState.ChanType.IsTaproot() {
26✔
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
                }
×
1314
        } else {
26✔
1315
                multiSigScript, err := input.GenMultiSigScript(
26✔
1316
                        localKey.SerializeCompressed(),
26✔
1317
                        remoteKey.SerializeCompressed(),
26✔
1318
                )
26✔
1319
                if err != nil {
26✔
1320
                        return nil, err
×
1321
                }
×
1322
                fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
26✔
1323
                if err != nil {
26✔
1324
                        return nil, err
×
1325
                }
×
1326
        }
1327

1328
        return fundingPkScript, nil
26✔
1329
}
1330

1331
// deriveHeightHint derives the block height for the channel opening.
1332
func deriveHeightHint(chanState *channeldb.OpenChannel) uint32 {
26✔
1333
        // As a height hint, we'll try to use the opening height, but if the
26✔
1334
        // channel isn't yet open, then we'll use the height it was broadcast
26✔
1335
        // at. This may be an unconfirmed zero-conf channel.
26✔
1336
        heightHint := chanState.ShortChanID().BlockHeight
26✔
1337
        if heightHint == 0 {
26✔
UNCOV
1338
                heightHint = chanState.BroadcastHeight()
×
UNCOV
1339
        }
×
1340

1341
        // Since no zero-conf state is stored in a channel backup, the below
1342
        // logic will not be triggered for restored, zero-conf channels. Set
1343
        // the height hint for zero-conf channels.
1344
        if chanState.IsZeroConf() {
26✔
UNCOV
1345
                if chanState.ZeroConfConfirmed() {
×
UNCOV
1346
                        // If the zero-conf channel is confirmed, we'll use the
×
UNCOV
1347
                        // confirmed SCID's block height.
×
UNCOV
1348
                        heightHint = chanState.ZeroConfRealScid().BlockHeight
×
UNCOV
1349
                } else {
×
UNCOV
1350
                        // The zero-conf channel is unconfirmed. We'll need to
×
UNCOV
1351
                        // use the FundingBroadcastHeight.
×
UNCOV
1352
                        heightHint = chanState.BroadcastHeight()
×
UNCOV
1353
                }
×
1354
        }
1355

1356
        return heightHint
26✔
1357
}
1358

1359
// handleCommitSpend takes a spending tx of the funding output and handles the
1360
// channel close based on the closure type.
1361
func (c *chainWatcher) handleCommitSpend(
1362
        commitSpend *chainntnfs.SpendDetail) error {
15✔
1363

15✔
1364
        commitTxBroadcast := commitSpend.SpendingTx
15✔
1365

15✔
1366
        // First, we'll construct the chainset which includes all the data we
15✔
1367
        // need to dispatch an event to our subscribers about this possible
15✔
1368
        // channel close event.
15✔
1369
        chainSet, err := newChainSet(c.cfg.chanState)
15✔
1370
        if err != nil {
15✔
1371
                return fmt.Errorf("create commit set: %w", err)
×
1372
        }
×
1373

1374
        // Decode the state hint encoded within the commitment transaction to
1375
        // determine if this is a revoked state or not.
1376
        obfuscator := c.stateHintObfuscator
15✔
1377
        broadcastStateNum := c.cfg.extractStateNumHint(
15✔
1378
                commitTxBroadcast, obfuscator,
15✔
1379
        )
15✔
1380

15✔
1381
        // We'll go on to check whether it could be our own commitment that was
15✔
1382
        // published and know is confirmed.
15✔
1383
        ok, err := c.handleKnownLocalState(
15✔
1384
                commitSpend, broadcastStateNum, chainSet,
15✔
1385
        )
15✔
1386
        if err != nil {
15✔
1387
                return fmt.Errorf("handle known local state: %w", err)
×
1388
        }
×
1389
        if ok {
17✔
1390
                return nil
2✔
1391
        }
2✔
1392

1393
        // Now that we know it is neither a non-cooperative closure nor a local
1394
        // close with the latest state, we check if it is the remote that
1395
        // closed with any prior or current state.
1396
        ok, err = c.handleKnownRemoteState(
13✔
1397
                commitSpend, broadcastStateNum, chainSet,
13✔
1398
        )
13✔
1399
        if err != nil {
13✔
1400
                return fmt.Errorf("handle known remote state: %w", err)
×
1401
        }
×
1402
        if ok {
15✔
1403
                return nil
2✔
1404
        }
2✔
1405

1406
        // Next, we'll check to see if this is a cooperative channel closure or
1407
        // not. This is characterized by having an input sequence number that's
1408
        // finalized. This won't happen with regular commitment transactions
1409
        // due to the state hint encoding scheme.
1410
        switch commitTxBroadcast.TxIn[0].Sequence {
11✔
UNCOV
1411
        case wire.MaxTxInSequenceNum:
×
UNCOV
1412
                fallthrough
×
UNCOV
1413
        case mempool.MaxRBFSequence:
×
UNCOV
1414
                // TODO(roasbeef): rare but possible, need itest case for
×
UNCOV
1415
                err := c.dispatchCooperativeClose(commitSpend)
×
UNCOV
1416
                if err != nil {
×
1417
                        return fmt.Errorf("handle coop close: %w", err)
×
1418
                }
×
1419

UNCOV
1420
                return nil
×
1421
        }
1422

1423
        log.Warnf("Unknown commitment broadcast for ChannelPoint(%v) ",
11✔
1424
                c.cfg.chanState.FundingOutpoint)
11✔
1425

11✔
1426
        // We'll try to recover as best as possible from losing state.  We
11✔
1427
        // first check if this was a local unknown state. This could happen if
11✔
1428
        // we force close, then lose state or attempt recovery before the
11✔
1429
        // commitment confirms.
11✔
1430
        ok, err = c.handleUnknownLocalState(
11✔
1431
                commitSpend, broadcastStateNum, chainSet,
11✔
1432
        )
11✔
1433
        if err != nil {
11✔
1434
                return fmt.Errorf("handle known local state: %w", err)
×
1435
        }
×
1436
        if ok {
18✔
1437
                return nil
7✔
1438
        }
7✔
1439

1440
        // Since it was neither a known remote state, nor a local state that
1441
        // was published, it most likely mean we lost state and the remote node
1442
        // closed. In this case we must start the DLP protocol in hope of
1443
        // getting our money back.
1444
        ok, err = c.handleUnknownRemoteState(
4✔
1445
                commitSpend, broadcastStateNum, chainSet,
4✔
1446
        )
4✔
1447
        if err != nil {
4✔
1448
                return fmt.Errorf("handle unknown remote state: %w", err)
×
1449
        }
×
1450
        if ok {
8✔
1451
                return nil
4✔
1452
        }
4✔
1453

1454
        log.Errorf("Unable to handle spending tx %v of channel point %v",
×
1455
                commitTxBroadcast.TxHash(), c.cfg.chanState.FundingOutpoint)
×
1456

×
1457
        return nil
×
1458
}
1459

1460
// checkFundingSpend performs a non-blocking read on the spendNtfn channel to
1461
// check whether there's a commit spend already. Returns the spend details if
1462
// found.
1463
func (c *chainWatcher) checkFundingSpend() *chainntnfs.SpendDetail {
15✔
1464
        select {
15✔
1465
        // We've detected a spend of the channel onchain! Depending on the type
1466
        // of spend, we'll act accordingly, so we'll examine the spending
1467
        // transaction to determine what we should do.
1468
        //
1469
        // TODO(Roasbeef): need to be able to ensure this only triggers
1470
        // on confirmation, to ensure if multiple txns are broadcast, we
1471
        // act on the one that's timestamped
UNCOV
1472
        case spend, ok := <-c.fundingSpendNtfn.Spend:
×
UNCOV
1473
                // If the channel was closed, then this means that the notifier
×
UNCOV
1474
                // exited, so we will as well.
×
UNCOV
1475
                if !ok {
×
1476
                        return nil
×
1477
                }
×
1478

UNCOV
1479
                log.Debugf("Found spend details for funding output: %v",
×
UNCOV
1480
                        spend.SpenderTxHash)
×
UNCOV
1481

×
UNCOV
1482
                return spend
×
1483

1484
        default:
15✔
1485
        }
1486

1487
        return nil
15✔
1488
}
1489

1490
// chanPointConfirmed checks whether the given channel point has confirmed.
1491
// This is used to ensure that the funding output has confirmed on chain before
1492
// we proceed with the rest of the close observer logic for taproot channels.
1493
// Check the docs in `fundingConfirmedNtfn` for details.
UNCOV
1494
func (c *chainWatcher) chanPointConfirmed() bool {
×
UNCOV
1495
        op := c.cfg.chanState.FundingOutpoint
×
UNCOV
1496

×
UNCOV
1497
        select {
×
UNCOV
1498
        case _, ok := <-c.fundingConfirmedNtfn.Confirmed:
×
UNCOV
1499
                // If the channel was closed, then this means that the notifier
×
UNCOV
1500
                // exited, so we will as well.
×
UNCOV
1501
                if !ok {
×
UNCOV
1502
                        return false
×
UNCOV
1503
                }
×
1504

UNCOV
1505
                log.Debugf("Taproot ChannelPoint(%v) confirmed", op)
×
UNCOV
1506

×
UNCOV
1507
                // The channel point has confirmed on chain. We now cancel the
×
UNCOV
1508
                // subscription.
×
UNCOV
1509
                c.fundingConfirmedNtfn.Cancel()
×
UNCOV
1510

×
UNCOV
1511
                return true
×
1512

UNCOV
1513
        default:
×
UNCOV
1514
                log.Infof("Taproot ChannelPoint(%v) not confirmed yet", op)
×
UNCOV
1515

×
UNCOV
1516
                return false
×
1517
        }
1518
}
1519

1520
// handleBlockbeat takes a blockbeat and queries for a spending tx for the
1521
// funding output. If the spending tx is found, it will be handled based on the
1522
// closure type.
1523
func (c *chainWatcher) handleBlockbeat(beat chainio.Blockbeat) {
15✔
1524
        // Notify the chain watcher has processed the block.
15✔
1525
        defer c.NotifyBlockProcessed(beat, nil)
15✔
1526

15✔
1527
        // If we have a fundingConfirmedNtfn, it means this is a taproot
15✔
1528
        // channel that is pending, before we proceed, we want to ensure that
15✔
1529
        // the expected funding output has confirmed on chain. Check the docs
15✔
1530
        // in `fundingConfirmedNtfn` for details.
15✔
1531
        if c.fundingConfirmedNtfn != nil {
15✔
UNCOV
1532
                // If the funding output hasn't confirmed in this block, we
×
UNCOV
1533
                // will check it again in the next block.
×
UNCOV
1534
                if !c.chanPointConfirmed() {
×
UNCOV
1535
                        return
×
UNCOV
1536
                }
×
1537
        }
1538

1539
        // Perform a non-blocking read to check whether the funding output was
1540
        // spent.
1541
        spend := c.checkFundingSpend()
15✔
1542
        if spend == nil {
30✔
1543
                log.Tracef("No spend found for ChannelPoint(%v) in block %v",
15✔
1544
                        c.cfg.chanState.FundingOutpoint, beat.Height())
15✔
1545

15✔
1546
                return
15✔
1547
        }
15✔
1548

1549
        // The funding output was spent, we now handle it by sending a close
1550
        // event to the channel arbitrator.
UNCOV
1551
        err := c.handleCommitSpend(spend)
×
UNCOV
1552
        if err != nil {
×
1553
                log.Errorf("Failed to handle commit spend: %v", err)
×
1554
        }
×
1555
}
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