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

lightningnetwork / lnd / 18852986778

27 Oct 2025 07:10PM UTC coverage: 54.859% (-11.8%) from 66.648%
18852986778

Pull #10265

github

web-flow
Merge 45787b3d5 into 9a7b526c0
Pull Request #10265: multi: update close logic to handle re-orgs of depth n-1, where n is num confs - add min conf floor

529 of 828 new or added lines in 17 files covered. (63.89%)

24026 existing lines in 286 files now uncovered.

110927 of 202205 relevant lines covered (54.86%)

21658.16 hits per line

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

71.4
/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
// spendConfirmationState represents the state of spend confirmation tracking
92
// in the closeObserver state machine. We wait for N confirmations before
93
// processing any spend to protect against shallow reorgs.
94
type spendConfirmationState uint8
95

96
const (
97
        // spendStateNone indicates no spend has been detected yet.
98
        spendStateNone spendConfirmationState = iota
99

100
        // spendStatePending indicates a spend has been detected and we're
101
        // waiting for the required number of confirmations.
102
        spendStatePending
103

104
        // spendStateConfirmed indicates the spend has reached the required
105
        // confirmations and has been processed.
106
        spendStateConfirmed
107
)
108

109
// String returns a human-readable representation of the state.
NEW
110
func (s spendConfirmationState) String() string {
×
NEW
111
        switch s {
×
NEW
112
        case spendStateNone:
×
NEW
113
                return "None"
×
NEW
114
        case spendStatePending:
×
NEW
115
                return "Pending"
×
NEW
116
        case spendStateConfirmed:
×
NEW
117
                return "Confirmed"
×
NEW
118
        default:
×
NEW
119
                return "Unknown"
×
120
        }
121
}
122

123
// CommitSet is a collection of the set of known valid commitments at a given
124
// instant. If ConfCommitKey is set, then the commitment identified by the
125
// HtlcSetKey has hit the chain. This struct will be used to examine all live
126
// HTLCs to determine if any additional actions need to be made based on the
127
// remote party's commitments.
128
type CommitSet struct {
129
        // When the ConfCommitKey is set, it signals that the commitment tx was
130
        // confirmed in the chain.
131
        ConfCommitKey fn.Option[HtlcSetKey]
132

133
        // HtlcSets stores the set of all known active HTLC for each active
134
        // commitment at the time of channel closure.
135
        HtlcSets map[HtlcSetKey][]channeldb.HTLC
136
}
137

138
// IsEmpty returns true if there are no HTLCs at all within all commitments
139
// that are a part of this commitment diff.
140
func (c *CommitSet) IsEmpty() bool {
16✔
141
        if c == nil {
16✔
142
                return true
×
143
        }
×
144

145
        for _, htlcs := range c.HtlcSets {
24✔
146
                if len(htlcs) != 0 {
16✔
147
                        return false
8✔
148
                }
8✔
149
        }
150

151
        return true
8✔
152
}
153

154
// toActiveHTLCSets returns the set of all active HTLCs across all commitment
155
// transactions.
156
func (c *CommitSet) toActiveHTLCSets() map[HtlcSetKey]htlcSet {
14✔
157
        htlcSets := make(map[HtlcSetKey]htlcSet)
14✔
158

14✔
159
        for htlcSetKey, htlcs := range c.HtlcSets {
26✔
160
                htlcSets[htlcSetKey] = newHtlcSet(htlcs)
12✔
161
        }
12✔
162

163
        return htlcSets
14✔
164
}
165

166
// String return a human-readable representation of the CommitSet.
UNCOV
167
func (c *CommitSet) String() string {
×
UNCOV
168
        if c == nil {
×
UNCOV
169
                return "nil"
×
UNCOV
170
        }
×
171

172
        // Create a descriptive string for the ConfCommitKey.
UNCOV
173
        commitKey := "none"
×
UNCOV
174
        c.ConfCommitKey.WhenSome(func(k HtlcSetKey) {
×
UNCOV
175
                commitKey = k.String()
×
UNCOV
176
        })
×
177

178
        // Create a map to hold all the htlcs.
UNCOV
179
        htlcSet := make(map[string]string)
×
UNCOV
180
        for k, htlcs := range c.HtlcSets {
×
UNCOV
181
                // Create a map for this particular set.
×
UNCOV
182
                desc := make([]string, len(htlcs))
×
UNCOV
183
                for i, htlc := range htlcs {
×
UNCOV
184
                        desc[i] = fmt.Sprintf("%x", htlc.RHash)
×
UNCOV
185
                }
×
186

187
                // Add the description to the set key.
UNCOV
188
                htlcSet[k.String()] = fmt.Sprintf("count: %v, htlcs=%v",
×
UNCOV
189
                        len(htlcs), desc)
×
190
        }
191

UNCOV
192
        return fmt.Sprintf("ConfCommitKey=%v, HtlcSets=%v", commitKey, htlcSet)
×
193
}
194

195
// ChainEventSubscription is a struct that houses a subscription to be notified
196
// for any on-chain events related to a channel. There are three types of
197
// possible on-chain events: a cooperative channel closure, a unilateral
198
// channel closure, and a channel breach. The fourth type: a force close is
199
// locally initiated, so we don't provide any event stream for said event.
200
type ChainEventSubscription struct {
201
        // ChanPoint is that channel that chain events will be dispatched for.
202
        ChanPoint wire.OutPoint
203

204
        // RemoteUnilateralClosure is a channel that will be sent upon in the
205
        // event that the remote party's commitment transaction is confirmed.
206
        RemoteUnilateralClosure chan *RemoteUnilateralCloseInfo
207

208
        // LocalUnilateralClosure is a channel that will be sent upon in the
209
        // event that our commitment transaction is confirmed.
210
        LocalUnilateralClosure chan *LocalUnilateralCloseInfo
211

212
        // CooperativeClosure is a signal that will be sent upon once a
213
        // cooperative channel closure has been detected confirmed.
214
        CooperativeClosure chan *CooperativeCloseInfo
215

216
        // ContractBreach is a channel that will be sent upon if we detect a
217
        // contract breach. The struct sent across the channel contains all the
218
        // material required to bring the cheating channel peer to justice.
219
        ContractBreach chan *BreachCloseInfo
220

221
        // Cancel cancels the subscription to the event stream for a particular
222
        // channel. This method should be called once the caller no longer needs to
223
        // be notified of any on-chain events for a particular channel.
224
        Cancel func()
225
}
226

227
// chainWatcherConfig encapsulates all the necessary functions and interfaces
228
// needed to watch and act on on-chain events for a particular channel.
229
type chainWatcherConfig struct {
230
        // chanState is a snapshot of the persistent state of the channel that
231
        // we're watching. In the event of an on-chain event, we'll query the
232
        // database to ensure that we act using the most up to date state.
233
        chanState *channeldb.OpenChannel
234

235
        // notifier is a reference to the channel notifier that we'll use to be
236
        // notified of output spends and when transactions are confirmed.
237
        notifier chainntnfs.ChainNotifier
238

239
        // signer is the main signer instances that will be responsible for
240
        // signing any HTLC and commitment transaction generated by the state
241
        // machine.
242
        signer input.Signer
243

244
        // contractBreach is a method that will be called by the watcher if it
245
        // detects that a contract breach transaction has been confirmed. It
246
        // will only return a non-nil error when the BreachArbitrator has
247
        // preserved the necessary breach info for this channel point.
248
        contractBreach func(*lnwallet.BreachRetribution) error
249

250
        // isOurAddr is a function that returns true if the passed address is
251
        // known to us.
252
        isOurAddr func(btcutil.Address) bool
253

254
        // extractStateNumHint extracts the encoded state hint using the passed
255
        // obfuscater. This is used by the chain watcher to identify which
256
        // state was broadcast and confirmed on-chain.
257
        extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64
258

259
        // auxLeafStore can be used to fetch information for custom channels.
260
        auxLeafStore fn.Option[lnwallet.AuxLeafStore]
261

262
        // auxResolver is used to supplement contract resolution.
263
        auxResolver fn.Option[lnwallet.AuxContractResolver]
264

265
        // chanCloseConfs is an optional override for the number of
266
        // confirmations required for channel closes. When set, this overrides
267
        // the normal capacity-based scaling. This is only available in
268
        // dev/integration builds for testing purposes.
269
        chanCloseConfs fn.Option[uint32]
270
}
271

272
// chainWatcher is a system that's assigned to every active channel. The duty
273
// of this system is to watch the chain for spends of the channels chan point.
274
// If a spend is detected then with chain watcher will notify all subscribers
275
// that the channel has been closed, and also give them the materials necessary
276
// to sweep the funds of the channel on chain eventually.
277
type chainWatcher struct {
278
        started int32 // To be used atomically.
279
        stopped int32 // To be used atomically.
280

281
        // Embed the blockbeat consumer struct to get access to the method
282
        // `NotifyBlockProcessed` and the `BlockbeatChan`.
283
        chainio.BeatConsumer
284

285
        quit chan struct{}
286
        wg   sync.WaitGroup
287

288
        cfg chainWatcherConfig
289

290
        // stateHintObfuscator is a 48-bit state hint that's used to obfuscate
291
        // the current state number on the commitment transactions.
292
        stateHintObfuscator [lnwallet.StateHintSize]byte
293

294
        // All the fields below are protected by this mutex.
295
        sync.Mutex
296

297
        // clientID is an ephemeral counter used to keep track of each
298
        // individual client subscription.
299
        clientID uint64
300

301
        // clientSubscriptions is a map that keeps track of all the active
302
        // client subscriptions for events related to this channel.
303
        clientSubscriptions map[uint64]*ChainEventSubscription
304

305
        // fundingSpendNtfn is the spending notification subscription for the
306
        // funding outpoint.
307
        fundingSpendNtfn *chainntnfs.SpendEvent
308

309
        // fundingConfirmedNtfn is the confirmation notification subscription
310
        // for the funding outpoint. This is only created if the channel is
311
        // both taproot and pending confirmation.
312
        //
313
        // For taproot pkscripts, `RegisterSpendNtfn` will only notify on the
314
        // outpoint being spent and not the outpoint+pkscript due to
315
        // `ComputePkScript` being unable to compute the pkscript if a key
316
        // spend is used. We need to add a `RegisterConfirmationsNtfn` here to
317
        // ensure that the outpoint+pkscript pair is confirmed before calling
318
        // `RegisterSpendNtfn`.
319
        fundingConfirmedNtfn *chainntnfs.ConfirmationEvent
320
}
321

322
// newChainWatcher returns a new instance of a chainWatcher for a channel given
323
// the chan point to watch, and also a notifier instance that will allow us to
324
// detect on chain events.
325
func newChainWatcher(cfg chainWatcherConfig) (*chainWatcher, error) {
141✔
326
        // In order to be able to detect the nature of a potential channel
141✔
327
        // closure we'll need to reconstruct the state hint bytes used to
141✔
328
        // obfuscate the commitment state number encoded in the lock time and
141✔
329
        // sequence fields.
141✔
330
        var stateHint [lnwallet.StateHintSize]byte
141✔
331
        chanState := cfg.chanState
141✔
332
        if chanState.IsInitiator {
282✔
333
                stateHint = lnwallet.DeriveStateHintObfuscator(
141✔
334
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
141✔
335
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
141✔
336
                )
141✔
337
        } else {
141✔
UNCOV
338
                stateHint = lnwallet.DeriveStateHintObfuscator(
×
UNCOV
339
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
×
UNCOV
340
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
×
UNCOV
341
                )
×
UNCOV
342
        }
×
343

344
        // Get the witness script for the funding output.
345
        fundingPkScript, err := deriveFundingPkScript(chanState)
141✔
346
        if err != nil {
141✔
347
                return nil, err
×
348
        }
×
349

350
        // Get the channel opening block height.
351
        heightHint := chanState.DeriveHeightHint()
141✔
352

141✔
353
        // We'll register for a notification to be dispatched if the funding
141✔
354
        // output is spent.
141✔
355
        spendNtfn, err := cfg.notifier.RegisterSpendNtfn(
141✔
356
                &chanState.FundingOutpoint, fundingPkScript, heightHint,
141✔
357
        )
141✔
358
        if err != nil {
141✔
359
                return nil, err
×
360
        }
×
361

362
        c := &chainWatcher{
141✔
363
                cfg:                 cfg,
141✔
364
                stateHintObfuscator: stateHint,
141✔
365
                quit:                make(chan struct{}),
141✔
366
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
141✔
367
                fundingSpendNtfn:    spendNtfn,
141✔
368
        }
141✔
369

141✔
370
        // If this is a pending taproot channel, we need to register for a
141✔
371
        // confirmation notification of the funding tx. Check the docs in
141✔
372
        // `fundingConfirmedNtfn` for details.
141✔
373
        if c.cfg.chanState.IsPending && c.cfg.chanState.ChanType.IsTaproot() {
141✔
UNCOV
374
                confNtfn, err := cfg.notifier.RegisterConfirmationsNtfn(
×
UNCOV
375
                        &chanState.FundingOutpoint.Hash, fundingPkScript, 1,
×
UNCOV
376
                        heightHint,
×
UNCOV
377
                )
×
UNCOV
378
                if err != nil {
×
379
                        return nil, err
×
380
                }
×
381

UNCOV
382
                c.fundingConfirmedNtfn = confNtfn
×
383
        }
384

385
        // Mount the block consumer.
386
        c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
141✔
387

141✔
388
        return c, nil
141✔
389
}
390

391
// Compile-time check for the chainio.Consumer interface.
392
var _ chainio.Consumer = (*chainWatcher)(nil)
393

394
// Name returns the name of the watcher.
395
//
396
// NOTE: part of the `chainio.Consumer` interface.
397
func (c *chainWatcher) Name() string {
141✔
398
        return fmt.Sprintf("ChainWatcher(%v)", c.cfg.chanState.FundingOutpoint)
141✔
399
}
141✔
400

401
// Start starts all goroutines that the chainWatcher needs to perform its
402
// duties.
403
func (c *chainWatcher) Start() error {
141✔
404
        if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
141✔
405
                return nil
×
406
        }
×
407

408
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
141✔
409
                c.cfg.chanState.FundingOutpoint)
141✔
410

141✔
411
        c.wg.Add(1)
141✔
412
        go c.closeObserver()
141✔
413

141✔
414
        return nil
141✔
415
}
416

417
// Stop signals the close observer to gracefully exit.
418
func (c *chainWatcher) Stop() error {
141✔
419
        if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
141✔
420
                return nil
×
421
        }
×
422

423
        close(c.quit)
141✔
424

141✔
425
        c.wg.Wait()
141✔
426

141✔
427
        return nil
141✔
428
}
429

430
// SubscribeChannelEvents returns an active subscription to the set of channel
431
// events for the channel watched by this chain watcher. Once clients no longer
432
// require the subscription, they should call the Cancel() method to allow the
433
// watcher to regain those committed resources.
434
func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
141✔
435

141✔
436
        c.Lock()
141✔
437
        clientID := c.clientID
141✔
438
        c.clientID++
141✔
439
        c.Unlock()
141✔
440

141✔
441
        log.Debugf("New ChainEventSubscription(id=%v) for ChannelPoint(%v)",
141✔
442
                clientID, c.cfg.chanState.FundingOutpoint)
141✔
443

141✔
444
        sub := &ChainEventSubscription{
141✔
445
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
141✔
446
                RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
141✔
447
                LocalUnilateralClosure:  make(chan *LocalUnilateralCloseInfo, 1),
141✔
448
                CooperativeClosure:      make(chan *CooperativeCloseInfo, 1),
141✔
449
                ContractBreach:          make(chan *BreachCloseInfo, 1),
141✔
450
                Cancel: func() {
152✔
451
                        c.Lock()
11✔
452
                        delete(c.clientSubscriptions, clientID)
11✔
453
                        c.Unlock()
11✔
454
                },
11✔
455
        }
456

457
        c.Lock()
141✔
458
        c.clientSubscriptions[clientID] = sub
141✔
459
        c.Unlock()
141✔
460

141✔
461
        return sub
141✔
462
}
463

464
// handleUnknownLocalState checks whether the passed spend _could_ be a local
465
// state that for some reason is unknown to us. This could be a state published
466
// by us before we lost state, which we will try to sweep. Or it could be one
467
// of our revoked states that somehow made it to the chain. If that's the case
468
// we cannot really hope that we'll be able to get our money back, but we'll
469
// try to sweep it anyway. If this is not an unknown local state, false is
470
// returned.
471
func (c *chainWatcher) handleUnknownLocalState(
472
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
473
        chainSet *chainSet) (bool, error) {
11✔
474

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

11✔
480
        // First, we'll re-derive our commitment point for this state since
11✔
481
        // this is what we use to randomize each of the keys for this state.
11✔
482
        commitSecret, err := c.cfg.chanState.RevocationProducer.AtIndex(
11✔
483
                broadcastStateNum,
11✔
484
        )
11✔
485
        if err != nil {
11✔
486
                return false, err
×
487
        }
×
488
        commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
11✔
489

11✔
490
        // Now that we have the commit point, we'll derive the tweaked local
11✔
491
        // and remote keys for this state. We use our point as only we can
11✔
492
        // revoke our own commitment.
11✔
493
        commitKeyRing := lnwallet.DeriveCommitmentKeys(
11✔
494
                commitPoint, lntypes.Local, c.cfg.chanState.ChanType,
11✔
495
                &c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
11✔
496
        )
11✔
497

11✔
498
        auxResult, err := fn.MapOptionZ(
11✔
499
                c.cfg.auxLeafStore,
11✔
500
                //nolint:ll
11✔
501
                func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
11✔
502
                        return s.FetchLeavesFromCommit(
×
503
                                lnwallet.NewAuxChanState(c.cfg.chanState),
×
504
                                c.cfg.chanState.LocalCommitment, *commitKeyRing,
×
505
                                lntypes.Local,
×
506
                        )
×
507
                },
×
508
        ).Unpack()
509
        if err != nil {
11✔
510
                return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
×
511
        }
×
512

513
        // With the keys derived, we'll construct the remote script that'll be
514
        // present if they have a non-dust balance on the commitment.
515
        var leaseExpiry uint32
11✔
516
        if c.cfg.chanState.ChanType.HasLeaseExpiration() {
11✔
UNCOV
517
                leaseExpiry = c.cfg.chanState.ThawHeight
×
UNCOV
518
        }
×
519

520
        remoteAuxLeaf := fn.FlatMapOption(
11✔
521
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
11✔
522
                        return l.RemoteAuxLeaf
×
523
                },
×
524
        )(auxResult.AuxLeaves)
525
        remoteScript, _, err := lnwallet.CommitScriptToRemote(
11✔
526
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
11✔
527
                commitKeyRing.ToRemoteKey, leaseExpiry,
11✔
528
                remoteAuxLeaf,
11✔
529
        )
11✔
530
        if err != nil {
11✔
531
                return false, err
×
532
        }
×
533

534
        // Next, we'll derive our script that includes the revocation base for
535
        // the remote party allowing them to claim this output before the CSV
536
        // delay if we breach.
537
        localAuxLeaf := fn.FlatMapOption(
11✔
538
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
11✔
539
                        return l.LocalAuxLeaf
×
540
                },
×
541
        )(auxResult.AuxLeaves)
542
        localScript, err := lnwallet.CommitScriptToSelf(
11✔
543
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
11✔
544
                commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
11✔
545
                uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
11✔
546
                localAuxLeaf,
11✔
547
        )
11✔
548
        if err != nil {
11✔
549
                return false, err
×
550
        }
×
551

552
        // With all our scripts assembled, we'll examine the outputs of the
553
        // commitment transaction to determine if this is a local force close
554
        // or not.
555
        ourCommit := false
11✔
556
        for _, output := range commitSpend.SpendingTx.TxOut {
27✔
557
                pkScript := output.PkScript
16✔
558

16✔
559
                switch {
16✔
560
                case bytes.Equal(localScript.PkScript(), pkScript):
4✔
561
                        ourCommit = true
4✔
562

563
                case bytes.Equal(remoteScript.PkScript(), pkScript):
4✔
564
                        ourCommit = true
4✔
565
                }
566
        }
567

568
        // If the script is not present, this cannot be our commit.
569
        if !ourCommit {
15✔
570
                return false, nil
4✔
571
        }
4✔
572

573
        log.Warnf("Detected local unilateral close of unknown state %v "+
7✔
574
                "(our state=%v)", broadcastStateNum,
7✔
575
                chainSet.localCommit.CommitHeight)
7✔
576

7✔
577
        // If this is our commitment transaction, then we try to act even
7✔
578
        // though we won't be able to sweep HTLCs.
7✔
579
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
7✔
580
        if err := c.dispatchLocalForceClose(
7✔
581
                commitSpend, broadcastStateNum, chainSet.commitSet,
7✔
582
        ); err != nil {
7✔
583
                return false, fmt.Errorf("unable to handle local"+
×
584
                        "close for chan_point=%v: %v",
×
585
                        c.cfg.chanState.FundingOutpoint, err)
×
586
        }
×
587

588
        return true, nil
7✔
589
}
590

591
// chainSet includes all the information we need to dispatch a channel close
592
// event to any subscribers.
593
type chainSet struct {
594
        // remoteStateNum is the commitment number of the lowest valid
595
        // commitment the remote party holds from our PoV. This value is used
596
        // to determine if the remote party is playing a state that's behind,
597
        // in line, or ahead of the latest state we know for it.
598
        remoteStateNum uint64
599

600
        // commitSet includes information pertaining to the set of active HTLCs
601
        // on each commitment.
602
        commitSet CommitSet
603

604
        // remoteCommit is the current commitment of the remote party.
605
        remoteCommit channeldb.ChannelCommitment
606

607
        // localCommit is our current commitment.
608
        localCommit channeldb.ChannelCommitment
609

610
        // remotePendingCommit points to the dangling commitment of the remote
611
        // party, if it exists. If there's no dangling commitment, then this
612
        // pointer will be nil.
613
        remotePendingCommit *channeldb.ChannelCommitment
614
}
615

616
// newChainSet creates a new chainSet given the current up to date channel
617
// state.
618
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
130✔
619
        // First, we'll grab the current unrevoked commitments for ourselves
130✔
620
        // and the remote party.
130✔
621
        localCommit, remoteCommit, err := chanState.LatestCommitments()
130✔
622
        if err != nil {
130✔
623
                return nil, fmt.Errorf("unable to fetch channel state for "+
×
624
                        "chan_point=%v: %v", chanState.FundingOutpoint, err)
×
625
        }
×
626

627
        log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
130✔
628
                chanState.FundingOutpoint, chanState.ChanType,
130✔
629
                lnutils.SpewLogClosure(localCommit))
130✔
630
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
130✔
631
                chanState.FundingOutpoint, chanState.ChanType,
130✔
632
                lnutils.SpewLogClosure(remoteCommit))
130✔
633

130✔
634
        // Fetch the current known commit height for the remote party, and
130✔
635
        // their pending commitment chain tip if it exists.
130✔
636
        remoteStateNum := remoteCommit.CommitHeight
130✔
637
        remoteChainTip, err := chanState.RemoteCommitChainTip()
130✔
638
        if err != nil && err != channeldb.ErrNoPendingCommit {
130✔
639
                return nil, fmt.Errorf("unable to obtain chain tip for "+
×
640
                        "ChannelPoint(%v): %v",
×
641
                        chanState.FundingOutpoint, err)
×
642
        }
×
643

644
        // Now that we have all the possible valid commitments, we'll make the
645
        // CommitSet the ChannelArbitrator will need in order to carry out its
646
        // duty.
647
        commitSet := CommitSet{
130✔
648
                HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
130✔
649
                        LocalHtlcSet:  localCommit.Htlcs,
130✔
650
                        RemoteHtlcSet: remoteCommit.Htlcs,
130✔
651
                },
130✔
652
        }
130✔
653

130✔
654
        var remotePendingCommit *channeldb.ChannelCommitment
130✔
655
        if remoteChainTip != nil {
131✔
656
                remotePendingCommit = &remoteChainTip.Commitment
1✔
657
                log.Tracef("ChannelPoint(%v): remote_pending_commit_type=%v, "+
1✔
658
                        "remote_pending_commit=%v", chanState.FundingOutpoint,
1✔
659
                        chanState.ChanType,
1✔
660
                        lnutils.SpewLogClosure(remoteChainTip.Commitment))
1✔
661

1✔
662
                htlcs := remoteChainTip.Commitment.Htlcs
1✔
663
                commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
1✔
664
        }
1✔
665

666
        // We'll now retrieve the latest state of the revocation store so we
667
        // can populate the revocation information within the channel state
668
        // object that we have.
669
        //
670
        // TODO(roasbeef): mutation is bad mkay
671
        _, err = chanState.RemoteRevocationStore()
130✔
672
        if err != nil {
130✔
673
                return nil, fmt.Errorf("unable to fetch revocation state for "+
×
674
                        "chan_point=%v", chanState.FundingOutpoint)
×
675
        }
×
676

677
        return &chainSet{
130✔
678
                remoteStateNum:      remoteStateNum,
130✔
679
                commitSet:           commitSet,
130✔
680
                localCommit:         *localCommit,
130✔
681
                remoteCommit:        *remoteCommit,
130✔
682
                remotePendingCommit: remotePendingCommit,
130✔
683
        }, nil
130✔
684
}
685

686
// closeObserver is a dedicated goroutine that will watch for any closes of the
687
// channel that it's watching on chain. It implements a state machine to handle
688
// spend detection and confirmation with reorg protection. The states are:
689
//
690
//   - None (confNtfn == nil): No spend detected yet, waiting for spend
691
//     notification
692
//
693
//   - Pending (confNtfn != nil): Spend detected, waiting for N confirmations
694
//
695
//   - Confirmed: Spend confirmed with N blocks, close has been processed
696
func (c *chainWatcher) closeObserver() {
141✔
697
        defer c.wg.Done()
141✔
698

141✔
699
        registerForSpend := func() (*chainntnfs.SpendEvent, error) {
348✔
700
                fundingPkScript, err := deriveFundingPkScript(c.cfg.chanState)
207✔
701
                if err != nil {
207✔
NEW
702
                        return nil, err
×
NEW
703
                }
×
704

705
                heightHint := c.cfg.chanState.DeriveHeightHint()
207✔
706

207✔
707
                return c.cfg.notifier.RegisterSpendNtfn(
207✔
708
                        &c.cfg.chanState.FundingOutpoint,
207✔
709
                        fundingPkScript,
207✔
710
                        heightHint,
207✔
711
                )
207✔
712
        }
713

714
        spendNtfn := c.fundingSpendNtfn
141✔
715
        defer spendNtfn.Cancel()
141✔
716

141✔
717
        // We use these variables to implement a state machine to track the
141✔
718
        // state of the spend confirmation process:
141✔
719
        //   * When confNtfn is nil, we're in state "None" waiting for a spend.
141✔
720
        //   * When confNtfn is set, we're in state "Pending" waiting for
141✔
721
        //     confirmations.
141✔
722
        //
141✔
723
        // After confirmations, we transition to state "Confirmed" and clean up.
141✔
724
        var (
141✔
725
                pendingSpend *chainntnfs.SpendDetail
141✔
726
                confNtfn     *chainntnfs.ConfirmationEvent
141✔
727
        )
141✔
728

141✔
729
        log.Infof("Close observer for ChannelPoint(%v) active",
141✔
730
                c.cfg.chanState.FundingOutpoint)
141✔
731

141✔
732
        // handleSpendDetection processes a newly detected spend by registering
141✔
733
        // for confirmations. Returns the new confNtfn or error.
141✔
734
        handleSpendDetection := func(
141✔
735
                spend *chainntnfs.SpendDetail,
141✔
736
        ) (*chainntnfs.ConfirmationEvent, error) {
478✔
737

337✔
738
                // If we already have a pending spend, check if it's the same
337✔
739
                // transaction. This can happen if both the spend notification
337✔
740
                // and blockbeat detect the same spend.
337✔
741
                if pendingSpend != nil {
337✔
NEW
742
                        if *pendingSpend.SpenderTxHash == *spend.SpenderTxHash {
×
NEW
743
                                log.Debugf("ChannelPoint(%v): ignoring "+
×
NEW
744
                                        "duplicate spend detection for tx %v",
×
NEW
745
                                        c.cfg.chanState.FundingOutpoint,
×
NEW
746
                                        spend.SpenderTxHash)
×
NEW
747
                                return confNtfn, nil
×
NEW
748
                        }
×
749

750
                        // Different spend detected. Cancel existing confNtfn
751
                        // and replace with new one.
NEW
752
                        log.Warnf("ChannelPoint(%v): detected different "+
×
NEW
753
                                "spend tx %v, replacing pending tx %v",
×
NEW
754
                                c.cfg.chanState.FundingOutpoint,
×
NEW
755
                                spend.SpenderTxHash,
×
NEW
756
                                pendingSpend.SpenderTxHash)
×
NEW
757

×
NEW
758
                        if confNtfn != nil {
×
NEW
759
                                confNtfn.Cancel()
×
NEW
760
                        }
×
761
                }
762

763
                numConfs := c.requiredConfsForSpend()
337✔
764
                txid := spend.SpenderTxHash
337✔
765

337✔
766
                newConfNtfn, err := c.cfg.notifier.RegisterConfirmationsNtfn(
337✔
767
                        txid, spend.SpendingTx.TxOut[0].PkScript,
337✔
768
                        numConfs, uint32(spend.SpendingHeight),
337✔
769
                )
337✔
770
                if err != nil {
337✔
NEW
771
                        return nil, fmt.Errorf("register confirmations: %w",
×
NEW
772
                                err)
×
NEW
773
                }
×
774

775
                log.Infof("ChannelPoint(%v): waiting for %d confirmations "+
337✔
776
                        "of spend tx %v", c.cfg.chanState.FundingOutpoint,
337✔
777
                        numConfs, txid)
337✔
778

337✔
779
                return newConfNtfn, nil
337✔
780
        }
781

782
        for {
971✔
783
                // We only listen to confirmation channels when we have a
830✔
784
                // pending spend. By setting these to nil when not needed, Go's
830✔
785
                // select ignores those cases, effectively implementing our
830✔
786
                // state machine.
830✔
787
                var (
830✔
788
                        confChan         <-chan *chainntnfs.TxConfirmation
830✔
789
                        negativeConfChan <-chan int32
830✔
790
                )
830✔
791
                if confNtfn != nil {
1,182✔
792
                        confChan = confNtfn.Confirmed
352✔
793
                        negativeConfChan = confNtfn.NegativeConf
352✔
794
                }
352✔
795

796
                select {
830✔
797
                case beat := <-c.BlockbeatChan:
15✔
798
                        log.Debugf("ChainWatcher(%v) received blockbeat %v",
15✔
799
                                c.cfg.chanState.FundingOutpoint, beat.Height())
15✔
800

15✔
801
                        spend := c.handleBlockbeat(beat)
15✔
802
                        if spend == nil {
30✔
803
                                continue
15✔
804
                        }
805

806
                        // STATE TRANSITION: None -> Pending (from blockbeat).
NEW
807
                        log.Infof("ChannelPoint(%v): detected spend from "+
×
NEW
808
                                "blockbeat, transitioning to %v",
×
NEW
809
                                c.cfg.chanState.FundingOutpoint,
×
NEW
810
                                spendStatePending)
×
NEW
811

×
NEW
812
                        newConfNtfn, err := handleSpendDetection(spend)
×
NEW
813
                        if err != nil {
×
NEW
814
                                log.Errorf("Unable to handle spend "+
×
NEW
815
                                        "detection: %v", err)
×
NEW
816
                                return
×
NEW
817
                        }
×
NEW
818
                        pendingSpend = spend
×
NEW
819
                        confNtfn = newConfNtfn
×
820

821
                // STATE TRANSITION: None -> Pending.
822
                // We've detected a spend, but don't process it yet. Instead,
823
                // register for confirmations to protect against shallow reorgs.
824
                case spend, ok := <-spendNtfn.Spend:
337✔
825
                        if !ok {
337✔
NEW
826
                                return
×
NEW
827
                        }
×
828

829
                        log.Infof("ChannelPoint(%v): detected spend from "+
337✔
830
                                "notification, transitioning to %v",
337✔
831
                                c.cfg.chanState.FundingOutpoint,
337✔
832
                                spendStatePending)
337✔
833

337✔
834
                        newConfNtfn, err := handleSpendDetection(spend)
337✔
835
                        if err != nil {
337✔
NEW
836
                                log.Errorf("Unable to handle spend "+
×
NEW
837
                                        "detection: %v", err)
×
NEW
838
                                return
×
NEW
839
                        }
×
840
                        pendingSpend = spend
337✔
841
                        confNtfn = newConfNtfn
337✔
842

843
                // STATE TRANSITION: Pending -> Confirmed
844
                // The spend has reached required confirmations. It's now safe
845
                // to process since we've protected against shallow reorgs.
846
                case conf, ok := <-confChan:
130✔
847
                        if !ok {
130✔
NEW
848
                                log.Errorf("Confirmation channel closed " +
×
NEW
849
                                        "unexpectedly")
×
850
                                return
×
851
                        }
×
852

853
                        log.Infof("ChannelPoint(%v): spend confirmed at "+
130✔
854
                                "height %d, transitioning to %v",
130✔
855
                                c.cfg.chanState.FundingOutpoint,
130✔
856
                                conf.BlockHeight, spendStateConfirmed)
130✔
857

130✔
858
                        err := c.handleCommitSpend(pendingSpend)
130✔
859
                        if err != nil {
130✔
NEW
860
                                log.Errorf("Failed to handle confirmed "+
×
NEW
861
                                        "spend: %v", err)
×
UNCOV
862
                        }
×
863

864
                        confNtfn.Cancel()
130✔
865
                        confNtfn = nil
130✔
866
                        pendingSpend = nil
130✔
867

868
                // STATE TRANSITION: Pending -> None
869
                // A reorg removed the spend tx. We reset to initial state and
870
                // wait for ANY new spend (could be the same tx re-mined, or a
871
                // different tx like an RBF replacement).
872
                case reorgDepth, ok := <-negativeConfChan:
207✔
873
                        if !ok {
207✔
NEW
874
                                log.Errorf("Negative conf channel closed " +
×
NEW
875
                                        "unexpectedly")
×
NEW
876
                                return
×
NEW
877
                        }
×
878

879
                        log.Infof("ChannelPoint(%v): spend reorged out at "+
207✔
880
                                "depth %d, transitioning back to %v",
207✔
881
                                c.cfg.chanState.FundingOutpoint, reorgDepth,
207✔
882
                                spendStateNone)
207✔
883

207✔
884
                        confNtfn.Cancel()
207✔
885
                        confNtfn = nil
207✔
886
                        pendingSpend = nil
207✔
887

207✔
888
                        spendNtfn.Cancel()
207✔
889
                        var err error
207✔
890
                        spendNtfn, err = registerForSpend()
207✔
891
                        if err != nil {
207✔
NEW
892
                                log.Errorf("Unable to re-register for "+
×
NEW
893
                                        "spend: %v", err)
×
NEW
894
                                return
×
NEW
895
                        }
×
896

897
                        log.Infof("ChannelPoint(%v): re-registered for spend "+
207✔
898
                                "detection", c.cfg.chanState.FundingOutpoint)
207✔
899

900
                // The chainWatcher has been signalled to exit, so we'll do so
901
                // now.
902
                case <-c.quit:
141✔
903
                        if confNtfn != nil {
141✔
NEW
904
                                confNtfn.Cancel()
×
NEW
905
                        }
×
906
                        return
141✔
907
                }
908
        }
909
}
910

911
// handleKnownLocalState checks whether the passed spend is a local state that
912
// is known to us (the current state). If so we will act on this state using
913
// the passed chainSet. If this is not a known local state, false is returned.
914
func (c *chainWatcher) handleKnownLocalState(
915
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
916
        chainSet *chainSet) (bool, error) {
130✔
917

130✔
918
        // If the channel is recovered, we won't have a local commit to check
130✔
919
        // against, so immediately return.
130✔
920
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
130✔
UNCOV
921
                return false, nil
×
UNCOV
922
        }
×
923

924
        commitTxBroadcast := commitSpend.SpendingTx
130✔
925
        commitHash := commitTxBroadcast.TxHash()
130✔
926

130✔
927
        // Check whether our latest local state hit the chain.
130✔
928
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
230✔
929
                return false, nil
100✔
930
        }
100✔
931

932
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
30✔
933
        if err := c.dispatchLocalForceClose(
30✔
934
                commitSpend, broadcastStateNum, chainSet.commitSet,
30✔
935
        ); err != nil {
30✔
936
                return false, fmt.Errorf("unable to handle local"+
×
937
                        "close for chan_point=%v: %v",
×
938
                        c.cfg.chanState.FundingOutpoint, err)
×
939
        }
×
940

941
        return true, nil
30✔
942
}
943

944
// handleKnownRemoteState checks whether the passed spend is a remote state
945
// that is known to us (a revoked, current or pending state). If so we will act
946
// on this state using the passed chainSet. If this is not a known remote
947
// state, false is returned.
948
func (c *chainWatcher) handleKnownRemoteState(
949
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
950
        chainSet *chainSet) (bool, error) {
100✔
951

100✔
952
        // If the channel is recovered, we won't have any remote commit to
100✔
953
        // check against, so imemdiately return.
100✔
954
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
100✔
UNCOV
955
                return false, nil
×
UNCOV
956
        }
×
957

958
        commitTxBroadcast := commitSpend.SpendingTx
100✔
959
        commitHash := commitTxBroadcast.TxHash()
100✔
960

100✔
961
        switch {
100✔
962
        // If the spending transaction matches the current latest state, then
963
        // they've initiated a unilateral close. So we'll trigger the
964
        // unilateral close signal so subscribers can clean up the state as
965
        // necessary.
966
        case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
29✔
967
                log.Infof("Remote party broadcast base set, "+
29✔
968
                        "commit_num=%v", chainSet.remoteStateNum)
29✔
969

29✔
970
                chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
29✔
971
                err := c.dispatchRemoteForceClose(
29✔
972
                        commitSpend, chainSet.remoteCommit,
29✔
973
                        chainSet.commitSet,
29✔
974
                        c.cfg.chanState.RemoteCurrentRevocation,
29✔
975
                )
29✔
976
                if err != nil {
29✔
977
                        return false, fmt.Errorf("unable to handle remote "+
×
978
                                "close for chan_point=%v: %v",
×
979
                                c.cfg.chanState.FundingOutpoint, err)
×
980
                }
×
981

982
                return true, nil
29✔
983

984
        // We'll also handle the case of the remote party broadcasting
985
        // their commitment transaction which is one height above ours.
986
        // This case can arise when we initiate a state transition, but
987
        // the remote party has a fail crash _after_ accepting the new
988
        // state, but _before_ sending their signature to us.
989
        case chainSet.remotePendingCommit != nil &&
990
                chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
1✔
991

1✔
992
                log.Infof("Remote party broadcast pending set, "+
1✔
993
                        "commit_num=%v", chainSet.remoteStateNum+1)
1✔
994

1✔
995
                chainSet.commitSet.ConfCommitKey = fn.Some(RemotePendingHtlcSet)
1✔
996
                err := c.dispatchRemoteForceClose(
1✔
997
                        commitSpend, *chainSet.remotePendingCommit,
1✔
998
                        chainSet.commitSet,
1✔
999
                        c.cfg.chanState.RemoteNextRevocation,
1✔
1000
                )
1✔
1001
                if err != nil {
1✔
1002
                        return false, fmt.Errorf("unable to handle remote "+
×
1003
                                "close for chan_point=%v: %v",
×
1004
                                c.cfg.chanState.FundingOutpoint, err)
×
1005
                }
×
1006

1007
                return true, nil
1✔
1008
        }
1009

1010
        // This is neither a remote force close or a "future" commitment, we
1011
        // now check whether it's a remote breach and properly handle it.
1012
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
70✔
1013
}
1014

1015
// handlePossibleBreach checks whether the remote has breached and dispatches a
1016
// breach resolution to claim funds.
1017
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
1018
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
70✔
1019

70✔
1020
        // We check if we have a revoked state at this state num that matches
70✔
1021
        // the spend transaction.
70✔
1022
        spendHeight := uint32(commitSpend.SpendingHeight)
70✔
1023
        retribution, err := lnwallet.NewBreachRetribution(
70✔
1024
                c.cfg.chanState, broadcastStateNum, spendHeight,
70✔
1025
                commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
70✔
1026
        )
70✔
1027

70✔
1028
        switch {
70✔
1029
        // If we had no log entry at this height, this was not a revoked state.
1030
        case err == channeldb.ErrLogEntryNotFound:
8✔
1031
                return false, nil
8✔
1032
        case err == channeldb.ErrNoPastDeltas:
41✔
1033
                return false, nil
41✔
1034

1035
        case err != nil:
×
1036
                return false, fmt.Errorf("unable to create breach "+
×
1037
                        "retribution: %v", err)
×
1038
        }
1039

1040
        // We found a revoked state at this height, but it could still be our
1041
        // own broadcasted state we are looking at. Therefore check that the
1042
        // commit matches before assuming it was a breach.
1043
        commitHash := commitSpend.SpendingTx.TxHash()
21✔
1044
        if retribution.BreachTxHash != commitHash {
21✔
1045
                return false, nil
×
1046
        }
×
1047

1048
        // Create an AnchorResolution for the breached state.
1049
        anchorRes, err := lnwallet.NewAnchorResolution(
21✔
1050
                c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
21✔
1051
                lntypes.Remote,
21✔
1052
        )
21✔
1053
        if err != nil {
21✔
1054
                return false, fmt.Errorf("unable to create anchor "+
×
1055
                        "resolution: %v", err)
×
1056
        }
×
1057

1058
        // We'll set the ConfCommitKey here as the remote htlc set. This is
1059
        // only used to ensure a nil-pointer-dereference doesn't occur and is
1060
        // not used otherwise. The HTLC's may not exist for the
1061
        // RemotePendingHtlcSet.
1062
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
21✔
1063

21✔
1064
        // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
21✔
1065
        // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
21✔
1066
        // broadcast to allow subscribers to swiftly dispatch justice!!!
21✔
1067
        err = c.dispatchContractBreach(
21✔
1068
                commitSpend, chainSet, broadcastStateNum, retribution,
21✔
1069
                anchorRes,
21✔
1070
        )
21✔
1071
        if err != nil {
21✔
1072
                return false, fmt.Errorf("unable to handle channel "+
×
1073
                        "breach for chan_point=%v: %v",
×
1074
                        c.cfg.chanState.FundingOutpoint, err)
×
1075
        }
×
1076

1077
        return true, nil
21✔
1078
}
1079

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

4✔
1089
        log.Warnf("Remote node broadcast state #%v, "+
4✔
1090
                "which is more than 1 beyond best known "+
4✔
1091
                "state #%v!!! Attempting recovery...",
4✔
1092
                broadcastStateNum, chainSet.remoteStateNum)
4✔
1093

4✔
1094
        // If this isn't a tweakless commitment, then we'll need to wait for
4✔
1095
        // the remote party's latest unrevoked commitment point to be presented
4✔
1096
        // to us as we need this to sweep. Otherwise, we can dispatch the
4✔
1097
        // remote close and sweep immediately using a fake commitPoint as it
4✔
1098
        // isn't actually needed for recovery anymore.
4✔
1099
        commitPoint := c.cfg.chanState.RemoteCurrentRevocation
4✔
1100
        tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
4✔
1101
        if !tweaklessCommit {
8✔
1102
                commitPoint = c.waitForCommitmentPoint()
4✔
1103
                if commitPoint == nil {
4✔
1104
                        return false, fmt.Errorf("unable to get commit point")
×
1105
                }
×
1106

1107
                log.Infof("Recovered commit point(%x) for "+
4✔
1108
                        "channel(%v)! Now attempting to use it to "+
4✔
1109
                        "sweep our funds...",
4✔
1110
                        commitPoint.SerializeCompressed(),
4✔
1111
                        c.cfg.chanState.FundingOutpoint)
4✔
UNCOV
1112
        } else {
×
UNCOV
1113
                log.Infof("ChannelPoint(%v) is tweakless, "+
×
UNCOV
1114
                        "moving to sweep directly on chain",
×
UNCOV
1115
                        c.cfg.chanState.FundingOutpoint)
×
UNCOV
1116
        }
×
1117

1118
        // Since we don't have the commitment stored for this state, we'll just
1119
        // pass an empty commitment within the commitment set. Note that this
1120
        // means we won't be able to recover any HTLC funds.
1121
        //
1122
        // TODO(halseth): can we try to recover some HTLCs?
1123
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
4✔
1124
        err := c.dispatchRemoteForceClose(
4✔
1125
                commitSpend, channeldb.ChannelCommitment{},
4✔
1126
                chainSet.commitSet, commitPoint,
4✔
1127
        )
4✔
1128
        if err != nil {
4✔
1129
                return false, fmt.Errorf("unable to handle remote "+
×
1130
                        "close for chan_point=%v: %v",
×
1131
                        c.cfg.chanState.FundingOutpoint, err)
×
1132
        }
×
1133

1134
        return true, nil
4✔
1135
}
1136

1137
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
1138
// to a script that the wallet controls or the channel defines as its delivery
1139
// script . If no outputs pay to us (determined by these criteria), then we
1140
// return zero. This is possible as our output may have been trimmed due to
1141
// being dust.
1142
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
38✔
1143
        // There are two main cases we have to handle here. First, in the coop
38✔
1144
        // close case we will always have saved the delivery address we used
38✔
1145
        // whether it was from the upfront shutdown, from the delivery address
38✔
1146
        // requested at close time, or even an automatically generated one. All
38✔
1147
        // coop-close cases can be identified in the following manner:
38✔
1148
        shutdown, _ := c.cfg.chanState.ShutdownInfo()
38✔
1149
        oDeliveryAddr := fn.MapOption(
38✔
1150
                func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
38✔
UNCOV
1151
                        return i.DeliveryScript.Val
×
UNCOV
1152
                })(shutdown)
×
1153

1154
        // Here we define a function capable of identifying whether an output
1155
        // corresponds with our local delivery script from a ShutdownInfo if we
1156
        // have a ShutdownInfo for this chainWatcher's underlying channel.
1157
        //
1158
        // isDeliveryOutput :: *TxOut -> bool
1159
        isDeliveryOutput := func(o *wire.TxOut) bool {
76✔
1160
                return fn.ElimOption(
38✔
1161
                        oDeliveryAddr,
38✔
1162
                        // If we don't have a delivery addr, then the output
38✔
1163
                        // can't match it.
38✔
1164
                        func() bool { return false },
76✔
1165
                        // Otherwise if the PkScript of the TxOut matches our
1166
                        // delivery script then this is a delivery output.
UNCOV
1167
                        func(a lnwire.DeliveryAddress) bool {
×
UNCOV
1168
                                return slices.Equal(a, o.PkScript)
×
UNCOV
1169
                        },
×
1170
                )
1171
        }
1172

1173
        // Here we define a function capable of identifying whether an output
1174
        // belongs to the LND wallet. We use this as a heuristic in the case
1175
        // where we might be looking for spendable force closure outputs.
1176
        //
1177
        // isWalletOutput :: *TxOut -> bool
1178
        isWalletOutput := func(out *wire.TxOut) bool {
76✔
1179
                _, addrs, _, err := txscript.ExtractPkScriptAddrs(
38✔
1180
                        // Doesn't matter what net we actually pass in.
38✔
1181
                        out.PkScript, &chaincfg.TestNet3Params,
38✔
1182
                )
38✔
1183
                if err != nil {
38✔
1184
                        return false
×
1185
                }
×
1186

1187
                return fn.Any(addrs, c.cfg.isOurAddr)
38✔
1188
        }
1189

1190
        // Grab all of the outputs that correspond with our delivery address
1191
        // or our wallet is aware of.
1192
        outs := fn.Filter(tx.TxOut, fn.PredOr(isDeliveryOutput, isWalletOutput))
38✔
1193

38✔
1194
        // Grab the values for those outputs.
38✔
1195
        vals := fn.Map(outs, func(o *wire.TxOut) int64 { return o.Value })
38✔
1196

1197
        // Return the sum.
1198
        return btcutil.Amount(fn.Sum(vals))
38✔
1199
}
1200

1201
// requiredConfsForSpend determines the number of confirmations required before
1202
// processing a spend of the funding output. Uses config override if set
1203
// (typically for testing), otherwise scales with channel capacity to balance
1204
// security vs user experience for channels of different sizes.
1205
func (c *chainWatcher) requiredConfsForSpend() uint32 {
337✔
1206
        return c.cfg.chanCloseConfs.UnwrapOrFunc(func() uint32 {
390✔
1207
                return lnwallet.CloseConfsForCapacity(
53✔
1208
                        c.cfg.chanState.Capacity,
53✔
1209
                )
53✔
1210
        })
53✔
1211
}
1212

1213
// dispatchCooperativeClose processed a detect cooperative channel closure.
1214
// We'll use the spending transaction to locate our output within the
1215
// transaction, then clean up the database state. We'll also dispatch a
1216
// notification to all subscribers that the channel has been closed in this
1217
// manner.
1218
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
38✔
1219
        broadcastTx := commitSpend.SpendingTx
38✔
1220

38✔
1221
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
38✔
1222
                c.cfg.chanState.FundingOutpoint,
38✔
1223
                lnutils.SpewLogClosure(broadcastTx))
38✔
1224

38✔
1225
        // If the input *is* final, then we'll check to see which output is
38✔
1226
        // ours.
38✔
1227
        localAmt := c.toSelfAmount(broadcastTx)
38✔
1228

38✔
1229
        // Once this is known, we'll mark the state as fully closed in the
38✔
1230
        // database. For cooperative closes, we wait for a confirmation depth
38✔
1231
        // determined by channel capacity before dispatching this event.
38✔
1232
        closeSummary := &channeldb.ChannelCloseSummary{
38✔
1233
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
38✔
1234
                ChainHash:               c.cfg.chanState.ChainHash,
38✔
1235
                ClosingTXID:             *commitSpend.SpenderTxHash,
38✔
1236
                RemotePub:               c.cfg.chanState.IdentityPub,
38✔
1237
                Capacity:                c.cfg.chanState.Capacity,
38✔
1238
                CloseHeight:             uint32(commitSpend.SpendingHeight),
38✔
1239
                SettledBalance:          localAmt,
38✔
1240
                CloseType:               channeldb.CooperativeClose,
38✔
1241
                ShortChanID:             c.cfg.chanState.ShortChanID(),
38✔
1242
                IsPending:               true,
38✔
1243
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
38✔
1244
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
38✔
1245
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
38✔
1246
        }
38✔
1247

38✔
1248
        // Attempt to add a channel sync message to the close summary.
38✔
1249
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
38✔
1250
        if err != nil {
38✔
1251
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1252
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1253
        } else {
38✔
1254
                closeSummary.LastChanSyncMsg = chanSync
38✔
1255
        }
38✔
1256

1257
        // Create a summary of all the information needed to handle the
1258
        // cooperative closure.
1259
        closeInfo := &CooperativeCloseInfo{
38✔
1260
                ChannelCloseSummary: closeSummary,
38✔
1261
        }
38✔
1262

38✔
1263
        // With the event processed, we'll now notify all subscribers of the
38✔
1264
        // event.
38✔
1265
        c.Lock()
38✔
1266
        for _, sub := range c.clientSubscriptions {
76✔
1267
                select {
38✔
1268
                case sub.CooperativeClosure <- closeInfo:
38✔
1269
                case <-c.quit:
×
1270
                        c.Unlock()
×
1271
                        return fmt.Errorf("exiting")
×
1272
                }
1273
        }
1274
        c.Unlock()
38✔
1275

38✔
1276
        return nil
38✔
1277
}
1278

1279
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
1280
func (c *chainWatcher) dispatchLocalForceClose(
1281
        commitSpend *chainntnfs.SpendDetail,
1282
        stateNum uint64, commitSet CommitSet) error {
37✔
1283

37✔
1284
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
37✔
1285
                "detected", c.cfg.chanState.FundingOutpoint)
37✔
1286

37✔
1287
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
37✔
1288
                c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
37✔
1289
                c.cfg.auxLeafStore, c.cfg.auxResolver,
37✔
1290
        )
37✔
1291
        if err != nil {
37✔
1292
                return err
×
1293
        }
×
1294

1295
        // As we've detected that the channel has been closed, immediately
1296
        // creating a close summary for future usage by related sub-systems.
1297
        chanSnapshot := forceClose.ChanSnapshot
37✔
1298
        closeSummary := &channeldb.ChannelCloseSummary{
37✔
1299
                ChanPoint:               chanSnapshot.ChannelPoint,
37✔
1300
                ChainHash:               chanSnapshot.ChainHash,
37✔
1301
                ClosingTXID:             forceClose.CloseTx.TxHash(),
37✔
1302
                RemotePub:               &chanSnapshot.RemoteIdentity,
37✔
1303
                Capacity:                chanSnapshot.Capacity,
37✔
1304
                CloseType:               channeldb.LocalForceClose,
37✔
1305
                IsPending:               true,
37✔
1306
                ShortChanID:             c.cfg.chanState.ShortChanID(),
37✔
1307
                CloseHeight:             uint32(commitSpend.SpendingHeight),
37✔
1308
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
37✔
1309
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
37✔
1310
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
37✔
1311
        }
37✔
1312

37✔
1313
        resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
37✔
1314
                fmt.Errorf("resolutions not found"),
37✔
1315
        )
37✔
1316
        if err != nil {
37✔
1317
                return err
×
1318
        }
×
1319

1320
        // If our commitment output isn't dust or we have active HTLC's on the
1321
        // commitment transaction, then we'll populate the balances on the
1322
        // close channel summary.
1323
        if resolutions.CommitResolution != nil {
71✔
1324
                localBalance := chanSnapshot.LocalBalance.ToSatoshis()
34✔
1325
                closeSummary.SettledBalance = localBalance
34✔
1326
                closeSummary.TimeLockedBalance = localBalance
34✔
1327
        }
34✔
1328

1329
        if resolutions.HtlcResolutions != nil {
74✔
1330
                for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
37✔
UNCOV
1331
                        htlcValue := btcutil.Amount(
×
UNCOV
1332
                                htlc.SweepSignDesc.Output.Value,
×
UNCOV
1333
                        )
×
UNCOV
1334
                        closeSummary.TimeLockedBalance += htlcValue
×
UNCOV
1335
                }
×
1336
        }
1337

1338
        // Attempt to add a channel sync message to the close summary.
1339
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
37✔
1340
        if err != nil {
37✔
1341
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1342
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1343
        } else {
37✔
1344
                closeSummary.LastChanSyncMsg = chanSync
37✔
1345
        }
37✔
1346

1347
        // With the event processed, we'll now notify all subscribers of the
1348
        // event.
1349
        closeInfo := &LocalUnilateralCloseInfo{
37✔
1350
                SpendDetail:            commitSpend,
37✔
1351
                LocalForceCloseSummary: forceClose,
37✔
1352
                ChannelCloseSummary:    closeSummary,
37✔
1353
                CommitSet:              commitSet,
37✔
1354
        }
37✔
1355
        c.Lock()
37✔
1356
        for _, sub := range c.clientSubscriptions {
74✔
1357
                select {
37✔
1358
                case sub.LocalUnilateralClosure <- closeInfo:
37✔
1359
                case <-c.quit:
×
1360
                        c.Unlock()
×
1361
                        return fmt.Errorf("exiting")
×
1362
                }
1363
        }
1364
        c.Unlock()
37✔
1365

37✔
1366
        return nil
37✔
1367
}
1368

1369
// dispatchRemoteForceClose processes a detected unilateral channel closure by
1370
// the remote party. This function will prepare a UnilateralCloseSummary which
1371
// will then be sent to any subscribers allowing them to resolve all our funds
1372
// in the channel on chain. Once this close summary is prepared, all registered
1373
// subscribers will receive a notification of this event. The commitPoint
1374
// argument should be set to the per_commitment_point corresponding to the
1375
// spending commitment.
1376
//
1377
// NOTE: The remoteCommit argument should be set to the stored commitment for
1378
// this particular state. If we don't have the commitment stored (should only
1379
// happen in case we have lost state) it should be set to an empty struct, in
1380
// which case we will attempt to sweep the non-HTLC output using the passed
1381
// commitPoint.
1382
func (c *chainWatcher) dispatchRemoteForceClose(
1383
        commitSpend *chainntnfs.SpendDetail,
1384
        remoteCommit channeldb.ChannelCommitment,
1385
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
34✔
1386

34✔
1387
        log.Infof("Unilateral close of ChannelPoint(%v) "+
34✔
1388
                "detected", c.cfg.chanState.FundingOutpoint)
34✔
1389

34✔
1390
        // First, we'll create a closure summary that contains all the
34✔
1391
        // materials required to let each subscriber sweep the funds in the
34✔
1392
        // channel on-chain.
34✔
1393
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
34✔
1394
                c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
34✔
1395
                commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
34✔
1396
        )
34✔
1397
        if err != nil {
34✔
1398
                return err
×
1399
        }
×
1400

1401
        // With the event processed, we'll now notify all subscribers of the
1402
        // event.
1403
        c.Lock()
34✔
1404
        for _, sub := range c.clientSubscriptions {
68✔
1405
                select {
34✔
1406
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
1407
                        UnilateralCloseSummary: uniClose,
1408
                        CommitSet:              commitSet,
1409
                }:
34✔
1410
                case <-c.quit:
×
1411
                        c.Unlock()
×
1412
                        return fmt.Errorf("exiting")
×
1413
                }
1414
        }
1415
        c.Unlock()
34✔
1416

34✔
1417
        return nil
34✔
1418
}
1419

1420
// dispatchContractBreach processes a detected contract breached by the remote
1421
// party. This method is to be called once we detect that the remote party has
1422
// broadcast a prior revoked commitment state. This method well prepare all the
1423
// materials required to bring the cheater to justice, then notify all
1424
// registered subscribers of this event.
1425
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
1426
        chainSet *chainSet, broadcastStateNum uint64,
1427
        retribution *lnwallet.BreachRetribution,
1428
        anchorRes *lnwallet.AnchorResolution) error {
21✔
1429

21✔
1430
        log.Warnf("Remote peer has breached the channel contract for "+
21✔
1431
                "ChannelPoint(%v). Revoked state #%v was broadcast!!!",
21✔
1432
                c.cfg.chanState.FundingOutpoint, broadcastStateNum)
21✔
1433

21✔
1434
        if err := c.cfg.chanState.MarkBorked(); err != nil {
21✔
1435
                return fmt.Errorf("unable to mark channel as borked: %w", err)
×
1436
        }
×
1437

1438
        spendHeight := uint32(spendEvent.SpendingHeight)
21✔
1439

21✔
1440
        log.Debugf("Punishment breach retribution created: %v",
21✔
1441
                lnutils.NewLogClosure(func() string {
21✔
UNCOV
1442
                        retribution.KeyRing.LocalHtlcKey = nil
×
UNCOV
1443
                        retribution.KeyRing.RemoteHtlcKey = nil
×
UNCOV
1444
                        retribution.KeyRing.ToLocalKey = nil
×
UNCOV
1445
                        retribution.KeyRing.ToRemoteKey = nil
×
UNCOV
1446
                        retribution.KeyRing.RevocationKey = nil
×
UNCOV
1447
                        return spew.Sdump(retribution)
×
UNCOV
1448
                }))
×
1449

1450
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
21✔
1451
        closeSummary := channeldb.ChannelCloseSummary{
21✔
1452
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
21✔
1453
                ChainHash:               c.cfg.chanState.ChainHash,
21✔
1454
                ClosingTXID:             *spendEvent.SpenderTxHash,
21✔
1455
                CloseHeight:             spendHeight,
21✔
1456
                RemotePub:               c.cfg.chanState.IdentityPub,
21✔
1457
                Capacity:                c.cfg.chanState.Capacity,
21✔
1458
                SettledBalance:          settledBalance,
21✔
1459
                CloseType:               channeldb.BreachClose,
21✔
1460
                IsPending:               true,
21✔
1461
                ShortChanID:             c.cfg.chanState.ShortChanID(),
21✔
1462
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
21✔
1463
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
21✔
1464
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
21✔
1465
        }
21✔
1466

21✔
1467
        // Attempt to add a channel sync message to the close summary.
21✔
1468
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
21✔
1469
        if err != nil {
21✔
1470
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1471
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1472
        } else {
21✔
1473
                closeSummary.LastChanSyncMsg = chanSync
21✔
1474
        }
21✔
1475

1476
        // Hand the retribution info over to the BreachArbitrator. This function
1477
        // will wait for a response from the breach arbiter and then proceed to
1478
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1479
        // will then mark the channel as closed after resolutions and the
1480
        // commit set are logged in the arbitrator log.
1481
        if err := c.cfg.contractBreach(retribution); err != nil {
21✔
1482
                log.Errorf("unable to hand breached contract off to "+
×
1483
                        "BreachArbitrator: %v", err)
×
1484
                return err
×
1485
        }
×
1486

1487
        breachRes := &BreachResolution{
21✔
1488
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
21✔
1489
        }
21✔
1490

21✔
1491
        breachInfo := &BreachCloseInfo{
21✔
1492
                CommitHash:       spendEvent.SpendingTx.TxHash(),
21✔
1493
                BreachResolution: breachRes,
21✔
1494
                AnchorResolution: anchorRes,
21✔
1495
                CommitSet:        chainSet.commitSet,
21✔
1496
                CloseSummary:     closeSummary,
21✔
1497
        }
21✔
1498

21✔
1499
        // With the event processed and channel closed, we'll now notify all
21✔
1500
        // subscribers of the event.
21✔
1501
        c.Lock()
21✔
1502
        for _, sub := range c.clientSubscriptions {
42✔
1503
                select {
21✔
1504
                case sub.ContractBreach <- breachInfo:
21✔
1505
                case <-c.quit:
×
1506
                        c.Unlock()
×
1507
                        return fmt.Errorf("quitting")
×
1508
                }
1509
        }
1510
        c.Unlock()
21✔
1511

21✔
1512
        return nil
21✔
1513
}
1514

1515
// waitForCommitmentPoint waits for the commitment point to be inserted into
1516
// the local database. We'll use this method in the DLP case, to wait for the
1517
// remote party to send us their point, as we can't proceed until we have that.
1518
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
4✔
1519
        // If we are lucky, the remote peer sent us the correct commitment
4✔
1520
        // point during channel sync, such that we can sweep our funds. If we
4✔
1521
        // cannot find the commit point, there's not much we can do other than
4✔
1522
        // wait for us to retrieve it. We will attempt to retrieve it from the
4✔
1523
        // peer each time we connect to it.
4✔
1524
        //
4✔
1525
        // TODO(halseth): actively initiate re-connection to the peer?
4✔
1526
        backoff := minCommitPointPollTimeout
4✔
1527
        for {
8✔
1528
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
4✔
1529
                if err == nil {
8✔
1530
                        return commitPoint
4✔
1531
                }
4✔
1532

1533
                log.Errorf("Unable to retrieve commitment point for "+
×
1534
                        "channel(%v) with lost state: %v. Retrying in %v.",
×
1535
                        c.cfg.chanState.FundingOutpoint, err, backoff)
×
1536

×
1537
                select {
×
1538
                // Wait before retrying, with an exponential backoff.
1539
                case <-time.After(backoff):
×
1540
                        backoff = 2 * backoff
×
1541
                        if backoff > maxCommitPointPollTimeout {
×
1542
                                backoff = maxCommitPointPollTimeout
×
1543
                        }
×
1544

1545
                case <-c.quit:
×
1546
                        return nil
×
1547
                }
1548
        }
1549
}
1550

1551
// deriveFundingPkScript derives the script used in the funding output.
1552
func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) {
348✔
1553
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
348✔
1554
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
348✔
1555

348✔
1556
        var (
348✔
1557
                err             error
348✔
1558
                fundingPkScript []byte
348✔
1559
        )
348✔
1560

348✔
1561
        if chanState.ChanType.IsTaproot() {
348✔
UNCOV
1562
                fundingPkScript, _, err = input.GenTaprootFundingScript(
×
UNCOV
1563
                        localKey, remoteKey, 0, chanState.TapscriptRoot,
×
UNCOV
1564
                )
×
UNCOV
1565
                if err != nil {
×
1566
                        return nil, err
×
1567
                }
×
1568
        } else {
348✔
1569
                multiSigScript, err := input.GenMultiSigScript(
348✔
1570
                        localKey.SerializeCompressed(),
348✔
1571
                        remoteKey.SerializeCompressed(),
348✔
1572
                )
348✔
1573
                if err != nil {
348✔
1574
                        return nil, err
×
1575
                }
×
1576
                fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
348✔
1577
                if err != nil {
348✔
1578
                        return nil, err
×
1579
                }
×
1580
        }
1581

1582
        return fundingPkScript, nil
348✔
1583
}
1584

1585
// handleCommitSpend takes a spending tx of the funding output and handles the
1586
// channel close based on the closure type.
1587
func (c *chainWatcher) handleCommitSpend(
1588
        commitSpend *chainntnfs.SpendDetail) error {
130✔
1589

130✔
1590
        commitTxBroadcast := commitSpend.SpendingTx
130✔
1591

130✔
1592
        // First, we'll construct the chainset which includes all the data we
130✔
1593
        // need to dispatch an event to our subscribers about this possible
130✔
1594
        // channel close event.
130✔
1595
        chainSet, err := newChainSet(c.cfg.chanState)
130✔
1596
        if err != nil {
130✔
1597
                return fmt.Errorf("create commit set: %w", err)
×
1598
        }
×
1599

1600
        // Decode the state hint encoded within the commitment transaction to
1601
        // determine if this is a revoked state or not.
1602
        obfuscator := c.stateHintObfuscator
130✔
1603
        broadcastStateNum := c.cfg.extractStateNumHint(
130✔
1604
                commitTxBroadcast, obfuscator,
130✔
1605
        )
130✔
1606

130✔
1607
        // We'll go on to check whether it could be our own commitment that was
130✔
1608
        // published and know is confirmed.
130✔
1609
        ok, err := c.handleKnownLocalState(
130✔
1610
                commitSpend, broadcastStateNum, chainSet,
130✔
1611
        )
130✔
1612
        if err != nil {
130✔
1613
                return fmt.Errorf("handle known local state: %w", err)
×
1614
        }
×
1615
        if ok {
160✔
1616
                return nil
30✔
1617
        }
30✔
1618

1619
        // Now that we know it is neither a non-cooperative closure nor a local
1620
        // close with the latest state, we check if it is the remote that
1621
        // closed with any prior or current state.
1622
        ok, err = c.handleKnownRemoteState(
100✔
1623
                commitSpend, broadcastStateNum, chainSet,
100✔
1624
        )
100✔
1625
        if err != nil {
100✔
1626
                return fmt.Errorf("handle known remote state: %w", err)
×
1627
        }
×
1628
        if ok {
151✔
1629
                return nil
51✔
1630
        }
51✔
1631

1632
        // Next, we'll check to see if this is a cooperative channel closure or
1633
        // not. This is characterized by having an input sequence number that's
1634
        // finalized. This won't happen with regular commitment transactions
1635
        // due to the state hint encoding scheme.
1636
        switch commitTxBroadcast.TxIn[0].Sequence {
49✔
1637
        case wire.MaxTxInSequenceNum:
38✔
1638
                fallthrough
38✔
1639
        case mempool.MaxRBFSequence:
38✔
1640
                // This is a cooperative close. Dispatch it directly - the
38✔
1641
                // confirmation waiting and reorg handling is done in the
38✔
1642
                // closeObserver state machine before we reach this point.
38✔
1643
                if err := c.dispatchCooperativeClose(commitSpend); err != nil {
38✔
1644
                        return fmt.Errorf("handle coop close: %w", err)
×
1645
                }
×
1646

1647
                return nil
38✔
1648
        }
1649

1650
        log.Warnf("Unknown commitment broadcast for ChannelPoint(%v) ",
11✔
1651
                c.cfg.chanState.FundingOutpoint)
11✔
1652

11✔
1653
        // We'll try to recover as best as possible from losing state.  We
11✔
1654
        // first check if this was a local unknown state. This could happen if
11✔
1655
        // we force close, then lose state or attempt recovery before the
11✔
1656
        // commitment confirms.
11✔
1657
        ok, err = c.handleUnknownLocalState(
11✔
1658
                commitSpend, broadcastStateNum, chainSet,
11✔
1659
        )
11✔
1660
        if err != nil {
11✔
1661
                return fmt.Errorf("handle known local state: %w", err)
×
1662
        }
×
1663
        if ok {
18✔
1664
                return nil
7✔
1665
        }
7✔
1666

1667
        // Since it was neither a known remote state, nor a local state that
1668
        // was published, it most likely mean we lost state and the remote node
1669
        // closed. In this case we must start the DLP protocol in hope of
1670
        // getting our money back.
1671
        ok, err = c.handleUnknownRemoteState(
4✔
1672
                commitSpend, broadcastStateNum, chainSet,
4✔
1673
        )
4✔
1674
        if err != nil {
4✔
1675
                return fmt.Errorf("handle unknown remote state: %w", err)
×
1676
        }
×
1677
        if ok {
8✔
1678
                return nil
4✔
1679
        }
4✔
1680

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

×
1684
        return nil
×
1685
}
1686

1687
// checkFundingSpend performs a non-blocking read on the spendNtfn channel to
1688
// check whether there's a commit spend already. Returns the spend details if
1689
// found.
1690
func (c *chainWatcher) checkFundingSpend() *chainntnfs.SpendDetail {
15✔
1691
        select {
15✔
1692
        // We've detected a spend of the channel onchain! Depending on the type
1693
        // of spend, we'll act accordingly, so we'll examine the spending
1694
        // transaction to determine what we should do.
1695
        //
1696
        // TODO(Roasbeef): need to be able to ensure this only triggers
1697
        // on confirmation, to ensure if multiple txns are broadcast, we
1698
        // act on the one that's timestamped
1699
        case spend, ok := <-c.fundingSpendNtfn.Spend:
×
1700
                // If the channel was closed, then this means that the notifier
×
1701
                // exited, so we will as well.
×
1702
                if !ok {
×
1703
                        return nil
×
1704
                }
×
1705

1706
                log.Debugf("Found spend details for funding output: %v",
×
1707
                        spend.SpenderTxHash)
×
1708

×
1709
                return spend
×
1710

1711
        default:
15✔
1712
        }
1713

1714
        return nil
15✔
1715
}
1716

1717
// chanPointConfirmed checks whether the given channel point has confirmed.
1718
// This is used to ensure that the funding output has confirmed on chain before
1719
// we proceed with the rest of the close observer logic for taproot channels.
1720
// Check the docs in `fundingConfirmedNtfn` for details.
UNCOV
1721
func (c *chainWatcher) chanPointConfirmed() bool {
×
UNCOV
1722
        op := c.cfg.chanState.FundingOutpoint
×
UNCOV
1723

×
UNCOV
1724
        select {
×
UNCOV
1725
        case _, ok := <-c.fundingConfirmedNtfn.Confirmed:
×
UNCOV
1726
                // If the channel was closed, then this means that the notifier
×
UNCOV
1727
                // exited, so we will as well.
×
UNCOV
1728
                if !ok {
×
UNCOV
1729
                        return false
×
UNCOV
1730
                }
×
1731

UNCOV
1732
                log.Debugf("Taproot ChannelPoint(%v) confirmed", op)
×
UNCOV
1733

×
UNCOV
1734
                // The channel point has confirmed on chain. We now cancel the
×
UNCOV
1735
                // subscription.
×
UNCOV
1736
                c.fundingConfirmedNtfn.Cancel()
×
UNCOV
1737

×
UNCOV
1738
                return true
×
1739

UNCOV
1740
        default:
×
UNCOV
1741
                log.Infof("Taproot ChannelPoint(%v) not confirmed yet", op)
×
UNCOV
1742

×
UNCOV
1743
                return false
×
1744
        }
1745
}
1746

1747
// handleBlockbeat takes a blockbeat and queries for a spending tx for the
1748
// funding output. If found, it returns the spend details so closeObserver can
1749
// process it. Returns nil if no spend was detected.
1750
func (c *chainWatcher) handleBlockbeat(beat chainio.Blockbeat) *chainntnfs.SpendDetail {
15✔
1751
        // Notify the chain watcher has processed the block.
15✔
1752
        defer c.NotifyBlockProcessed(beat, nil)
15✔
1753

15✔
1754
        // If we have a fundingConfirmedNtfn, it means this is a taproot
15✔
1755
        // channel that is pending, before we proceed, we want to ensure that
15✔
1756
        // the expected funding output has confirmed on chain. Check the docs
15✔
1757
        // in `fundingConfirmedNtfn` for details.
15✔
1758
        if c.fundingConfirmedNtfn != nil {
15✔
UNCOV
1759
                // If the funding output hasn't confirmed in this block, we
×
UNCOV
1760
                // will check it again in the next block.
×
UNCOV
1761
                if !c.chanPointConfirmed() {
×
NEW
1762
                        return nil
×
UNCOV
1763
                }
×
1764
        }
1765

1766
        // Perform a non-blocking read to check whether the funding output was
1767
        // spent. The actual spend handling is done in closeObserver's state
1768
        // machine to avoid blocking the block processing pipeline.
1769
        spend := c.checkFundingSpend()
15✔
1770
        if spend == nil {
30✔
1771
                log.Tracef("No spend found for ChannelPoint(%v) in block %v",
15✔
1772
                        c.cfg.chanState.FundingOutpoint, beat.Height())
15✔
1773

15✔
1774
                return nil
15✔
1775
        }
15✔
1776

NEW
1777
        log.Debugf("Detected spend of ChannelPoint(%v) in block %v",
×
NEW
1778
                c.cfg.chanState.FundingOutpoint, beat.Height())
×
NEW
1779

×
NEW
1780
        return spend
×
1781
}
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