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

lightningnetwork / lnd / 12154363699

04 Dec 2024 06:20AM UTC coverage: 57.909% (+0.05%) from 57.861%
12154363699

Pull #9307

github

yyforyongyu
itest: document a rare flake found in `macOS`
Pull Request #9307: Beat itest [2/3]: document and fix itest flakes

1 of 34 new or added lines in 5 files covered. (2.94%)

33 existing lines in 8 files now uncovered.

101561 of 175379 relevant lines covered (57.91%)

25134.67 hits per line

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

56.64
/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"
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
        fundingConfirmedNtfn *chainntnfs.ConfirmationEvent
246
}
247

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

270
        // Get the witness script for the funding output.
271
        fundingPkScript, err := deriveFundingPkScript(chanState)
26✔
272
        if err != nil {
26✔
273
                return nil, err
×
274
        }
×
275

276
        // Get the channel opening block height.
277
        heightHint := deriveHeightHint(chanState)
26✔
278

26✔
279
        // We'll register for a notification to be dispatched if the funding
26✔
280
        // output is spent.
26✔
281
        spendNtfn, err := cfg.notifier.RegisterSpendNtfn(
26✔
282
                &chanState.FundingOutpoint, fundingPkScript, heightHint,
26✔
283
        )
26✔
284
        if err != nil {
26✔
285
                return nil, err
×
286
        }
×
287

288
        c := &chainWatcher{
26✔
289
                cfg:                 cfg,
26✔
290
                stateHintObfuscator: stateHint,
26✔
291
                quit:                make(chan struct{}),
26✔
292
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
26✔
293
                fundingSpendNtfn:    spendNtfn,
26✔
294
        }
26✔
295

26✔
296
        // If this is a pending taproot channel, we need to register for a
26✔
297
        // confirmation notification of the funding tx.
26✔
298
        if c.cfg.chanState.IsPending && c.cfg.chanState.ChanType.IsTaproot() {
26✔
299
                confNtfn, err := cfg.notifier.RegisterConfirmationsNtfn(
×
300
                        &chanState.FundingOutpoint.Hash, fundingPkScript, 1,
×
301
                        heightHint,
×
302
                )
×
303
                if err != nil {
×
304
                        return nil, err
×
305
                }
×
306

307
                c.fundingConfirmedNtfn = confNtfn
×
308
        }
309

310
        // Mount the block consumer.
311
        c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
26✔
312

26✔
313
        return c, nil
26✔
314
}
315

316
// Compile-time check for the chainio.Consumer interface.
317
var _ chainio.Consumer = (*chainWatcher)(nil)
318

319
// Name returns the name of the watcher.
320
//
321
// NOTE: part of the `chainio.Consumer` interface.
322
func (c *chainWatcher) Name() string {
26✔
323
        return fmt.Sprintf("ChainWatcher(%v)", c.cfg.chanState.FundingOutpoint)
26✔
324
}
26✔
325

326
// Start starts all goroutines that the chainWatcher needs to perform its
327
// duties.
328
func (c *chainWatcher) Start() error {
26✔
329
        if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
26✔
330
                return nil
×
331
        }
×
332

333
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
26✔
334
                c.cfg.chanState.FundingOutpoint)
26✔
335

26✔
336
        c.wg.Add(1)
26✔
337
        go c.closeObserver()
26✔
338

26✔
339
        return nil
26✔
340
}
341

342
// Stop signals the close observer to gracefully exit.
343
func (c *chainWatcher) Stop() error {
26✔
344
        if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
26✔
345
                return nil
×
346
        }
×
347

348
        close(c.quit)
26✔
349

26✔
350
        c.wg.Wait()
26✔
351

26✔
352
        return nil
26✔
353
}
354

355
// SubscribeChannelEvents returns an active subscription to the set of channel
356
// events for the channel watched by this chain watcher. Once clients no longer
357
// require the subscription, they should call the Cancel() method to allow the
358
// watcher to regain those committed resources.
359
func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
26✔
360

26✔
361
        c.Lock()
26✔
362
        clientID := c.clientID
26✔
363
        c.clientID++
26✔
364
        c.Unlock()
26✔
365

26✔
366
        log.Debugf("New ChainEventSubscription(id=%v) for ChannelPoint(%v)",
26✔
367
                clientID, c.cfg.chanState.FundingOutpoint)
26✔
368

26✔
369
        sub := &ChainEventSubscription{
26✔
370
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
26✔
371
                RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
26✔
372
                LocalUnilateralClosure:  make(chan *LocalUnilateralCloseInfo, 1),
26✔
373
                CooperativeClosure:      make(chan *CooperativeCloseInfo, 1),
26✔
374
                ContractBreach:          make(chan *BreachCloseInfo, 1),
26✔
375
                Cancel: func() {
37✔
376
                        c.Lock()
11✔
377
                        delete(c.clientSubscriptions, clientID)
11✔
378
                        c.Unlock()
11✔
379
                },
11✔
380
        }
381

382
        c.Lock()
26✔
383
        c.clientSubscriptions[clientID] = sub
26✔
384
        c.Unlock()
26✔
385

26✔
386
        return sub
26✔
387
}
388

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

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

11✔
405
        // First, we'll re-derive our commitment point for this state since
11✔
406
        // this is what we use to randomize each of the keys for this state.
11✔
407
        commitSecret, err := c.cfg.chanState.RevocationProducer.AtIndex(
11✔
408
                broadcastStateNum,
11✔
409
        )
11✔
410
        if err != nil {
11✔
411
                return false, err
×
412
        }
×
413
        commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
11✔
414

11✔
415
        // Now that we have the commit point, we'll derive the tweaked local
11✔
416
        // and remote keys for this state. We use our point as only we can
11✔
417
        // revoke our own commitment.
11✔
418
        commitKeyRing := lnwallet.DeriveCommitmentKeys(
11✔
419
                commitPoint, lntypes.Local, c.cfg.chanState.ChanType,
11✔
420
                &c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
11✔
421
        )
11✔
422

11✔
423
        auxResult, err := fn.MapOptionZ(
11✔
424
                c.cfg.auxLeafStore,
11✔
425
                //nolint:ll
11✔
426
                func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
11✔
427
                        return s.FetchLeavesFromCommit(
×
428
                                lnwallet.NewAuxChanState(c.cfg.chanState),
×
429
                                c.cfg.chanState.LocalCommitment, *commitKeyRing,
×
430
                                lntypes.Local,
×
431
                        )
×
432
                },
×
433
        ).Unpack()
434
        if err != nil {
11✔
435
                return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
×
436
        }
×
437

438
        // With the keys derived, we'll construct the remote script that'll be
439
        // present if they have a non-dust balance on the commitment.
440
        var leaseExpiry uint32
11✔
441
        if c.cfg.chanState.ChanType.HasLeaseExpiration() {
11✔
442
                leaseExpiry = c.cfg.chanState.ThawHeight
×
443
        }
×
444

445
        remoteAuxLeaf := fn.ChainOption(
11✔
446
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
11✔
447
                        return l.RemoteAuxLeaf
×
448
                },
×
449
        )(auxResult.AuxLeaves)
450
        remoteScript, _, err := lnwallet.CommitScriptToRemote(
11✔
451
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
11✔
452
                commitKeyRing.ToRemoteKey, leaseExpiry,
11✔
453
                remoteAuxLeaf,
11✔
454
        )
11✔
455
        if err != nil {
11✔
456
                return false, err
×
457
        }
×
458

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

477
        // With all our scripts assembled, we'll examine the outputs of the
478
        // commitment transaction to determine if this is a local force close
479
        // or not.
480
        ourCommit := false
11✔
481
        for _, output := range commitSpend.SpendingTx.TxOut {
27✔
482
                pkScript := output.PkScript
16✔
483

16✔
484
                switch {
16✔
485
                case bytes.Equal(localScript.PkScript(), pkScript):
4✔
486
                        ourCommit = true
4✔
487

488
                case bytes.Equal(remoteScript.PkScript(), pkScript):
4✔
489
                        ourCommit = true
4✔
490
                }
491
        }
492

493
        // If the script is not present, this cannot be our commit.
494
        if !ourCommit {
15✔
495
                return false, nil
4✔
496
        }
4✔
497

498
        log.Warnf("Detected local unilateral close of unknown state %v "+
7✔
499
                "(our state=%v)", broadcastStateNum,
7✔
500
                chainSet.localCommit.CommitHeight)
7✔
501

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

513
        return true, nil
7✔
514
}
515

516
// chainSet includes all the information we need to dispatch a channel close
517
// event to any subscribers.
518
type chainSet struct {
519
        // remoteStateNum is the commitment number of the lowest valid
520
        // commitment the remote party holds from our PoV. This value is used
521
        // to determine if the remote party is playing a state that's behind,
522
        // in line, or ahead of the latest state we know for it.
523
        remoteStateNum uint64
524

525
        // commitSet includes information pertaining to the set of active HTLCs
526
        // on each commitment.
527
        commitSet CommitSet
528

529
        // remoteCommit is the current commitment of the remote party.
530
        remoteCommit channeldb.ChannelCommitment
531

532
        // localCommit is our current commitment.
533
        localCommit channeldb.ChannelCommitment
534

535
        // remotePendingCommit points to the dangling commitment of the remote
536
        // party, if it exists. If there's no dangling commitment, then this
537
        // pointer will be nil.
538
        remotePendingCommit *channeldb.ChannelCommitment
539
}
540

541
// newChainSet creates a new chainSet given the current up to date channel
542
// state.
543
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
15✔
544
        // First, we'll grab the current unrevoked commitments for ourselves
15✔
545
        // and the remote party.
15✔
546
        localCommit, remoteCommit, err := chanState.LatestCommitments()
15✔
547
        if err != nil {
15✔
548
                return nil, fmt.Errorf("unable to fetch channel state for "+
×
549
                        "chan_point=%v: %v", chanState.FundingOutpoint, err)
×
550
        }
×
551

552
        log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
15✔
553
                chanState.FundingOutpoint, chanState.ChanType,
15✔
554
                spew.Sdump(localCommit))
15✔
555
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
15✔
556
                chanState.FundingOutpoint, chanState.ChanType,
15✔
557
                spew.Sdump(remoteCommit))
15✔
558

15✔
559
        // Fetch the current known commit height for the remote party, and
15✔
560
        // their pending commitment chain tip if it exists.
15✔
561
        remoteStateNum := remoteCommit.CommitHeight
15✔
562
        remoteChainTip, err := chanState.RemoteCommitChainTip()
15✔
563
        if err != nil && err != channeldb.ErrNoPendingCommit {
15✔
564
                return nil, fmt.Errorf("unable to obtain chain tip for "+
×
565
                        "ChannelPoint(%v): %v",
×
566
                        chanState.FundingOutpoint, err)
×
567
        }
×
568

569
        // Now that we have all the possible valid commitments, we'll make the
570
        // CommitSet the ChannelArbitrator will need in order to carry out its
571
        // duty.
572
        commitSet := CommitSet{
15✔
573
                HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
15✔
574
                        LocalHtlcSet:  localCommit.Htlcs,
15✔
575
                        RemoteHtlcSet: remoteCommit.Htlcs,
15✔
576
                },
15✔
577
        }
15✔
578

15✔
579
        var remotePendingCommit *channeldb.ChannelCommitment
15✔
580
        if remoteChainTip != nil {
16✔
581
                remotePendingCommit = &remoteChainTip.Commitment
1✔
582
                log.Tracef("ChannelPoint(%v): remote_pending_commit_type=%v, "+
1✔
583
                        "remote_pending_commit=%v", chanState.FundingOutpoint,
1✔
584
                        chanState.ChanType,
1✔
585
                        spew.Sdump(remoteChainTip.Commitment))
1✔
586

1✔
587
                htlcs := remoteChainTip.Commitment.Htlcs
1✔
588
                commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
1✔
589
        }
1✔
590

591
        // We'll now retrieve the latest state of the revocation store so we
592
        // can populate the revocation information within the channel state
593
        // object that we have.
594
        //
595
        // TODO(roasbeef): mutation is bad mkay
596
        _, err = chanState.RemoteRevocationStore()
15✔
597
        if err != nil {
15✔
598
                return nil, fmt.Errorf("unable to fetch revocation state for "+
×
599
                        "chan_point=%v", chanState.FundingOutpoint)
×
600
        }
×
601

602
        return &chainSet{
15✔
603
                remoteStateNum:      remoteStateNum,
15✔
604
                commitSet:           commitSet,
15✔
605
                localCommit:         *localCommit,
15✔
606
                remoteCommit:        *remoteCommit,
15✔
607
                remotePendingCommit: remotePendingCommit,
15✔
608
        }, nil
15✔
609
}
610

611
// closeObserver is a dedicated goroutine that will watch for any closes of the
612
// channel that it's watching on chain. In the event of an on-chain event, the
613
// close observer will assembled the proper materials required to claim the
614
// funds of the channel on-chain (if required), then dispatch these as
615
// notifications to all subscribers.
616
func (c *chainWatcher) closeObserver() {
26✔
617
        defer c.wg.Done()
26✔
618
        defer c.fundingSpendNtfn.Cancel()
26✔
619

26✔
620
        log.Infof("Close observer for ChannelPoint(%v) active",
26✔
621
                c.cfg.chanState.FundingOutpoint)
26✔
622

26✔
623
        for {
82✔
624
                select {
56✔
625
                // A new block is received, we will check whether this block
626
                // contains a spending tx that we are interested in.
627
                case beat := <-c.BlockbeatChan:
15✔
628
                        log.Debugf("ChainWatcher(%v) received blockbeat %v",
15✔
629
                                c.cfg.chanState.FundingOutpoint, beat.Height())
15✔
630

15✔
631
                        // Process the block.
15✔
632
                        c.handleBlockbeat(beat)
15✔
633

634
                // If the funding outpoint is spent, we now go ahead and handle
635
                // it.
636
                case spend, ok := <-c.fundingSpendNtfn.Spend:
15✔
637
                        // If the channel was closed, then this means that the
15✔
638
                        // notifier exited, so we will as well.
15✔
639
                        if !ok {
15✔
640
                                return
×
641
                        }
×
642

643
                        err := c.handleCommitSpend(spend)
15✔
644
                        if err != nil {
15✔
645
                                log.Errorf("Failed to handle commit spend: %v",
×
646
                                        err)
×
647
                        }
×
648

649
                // The chainWatcher has been signalled to exit, so we'll do so
650
                // now.
651
                case <-c.quit:
26✔
652
                        return
26✔
653
                }
654
        }
655
}
656

657
// handleKnownLocalState checks whether the passed spend is a local state that
658
// is known to us (the current state). If so we will act on this state using
659
// the passed chainSet. If this is not a known local state, false is returned.
660
func (c *chainWatcher) handleKnownLocalState(
661
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
662
        chainSet *chainSet) (bool, error) {
15✔
663

15✔
664
        // If the channel is recovered, we won't have a local commit to check
15✔
665
        // against, so immediately return.
15✔
666
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
15✔
667
                return false, nil
×
668
        }
×
669

670
        commitTxBroadcast := commitSpend.SpendingTx
15✔
671
        commitHash := commitTxBroadcast.TxHash()
15✔
672

15✔
673
        // Check whether our latest local state hit the chain.
15✔
674
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
28✔
675
                return false, nil
13✔
676
        }
13✔
677

678
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
2✔
679
        if err := c.dispatchLocalForceClose(
2✔
680
                commitSpend, broadcastStateNum, chainSet.commitSet,
2✔
681
        ); err != nil {
2✔
682
                return false, fmt.Errorf("unable to handle local"+
×
683
                        "close for chan_point=%v: %v",
×
684
                        c.cfg.chanState.FundingOutpoint, err)
×
685
        }
×
686

687
        return true, nil
2✔
688
}
689

690
// handleKnownRemoteState checks whether the passed spend is a remote state
691
// that is known to us (a revoked, current or pending state). If so we will act
692
// on this state using the passed chainSet. If this is not a known remote
693
// state, false is returned.
694
func (c *chainWatcher) handleKnownRemoteState(
695
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
696
        chainSet *chainSet) (bool, error) {
13✔
697

13✔
698
        // If the channel is recovered, we won't have any remote commit to
13✔
699
        // check against, so imemdiately return.
13✔
700
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
13✔
701
                return false, nil
×
702
        }
×
703

704
        commitTxBroadcast := commitSpend.SpendingTx
13✔
705
        commitHash := commitTxBroadcast.TxHash()
13✔
706

13✔
707
        switch {
13✔
708
        // If the spending transaction matches the current latest state, then
709
        // they've initiated a unilateral close. So we'll trigger the
710
        // unilateral close signal so subscribers can clean up the state as
711
        // necessary.
712
        case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
1✔
713
                log.Infof("Remote party broadcast base set, "+
1✔
714
                        "commit_num=%v", chainSet.remoteStateNum)
1✔
715

1✔
716
                chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
1✔
717
                err := c.dispatchRemoteForceClose(
1✔
718
                        commitSpend, chainSet.remoteCommit,
1✔
719
                        chainSet.commitSet,
1✔
720
                        c.cfg.chanState.RemoteCurrentRevocation,
1✔
721
                )
1✔
722
                if err != nil {
1✔
723
                        return false, fmt.Errorf("unable to handle remote "+
×
724
                                "close for chan_point=%v: %v",
×
725
                                c.cfg.chanState.FundingOutpoint, err)
×
726
                }
×
727

728
                return true, nil
1✔
729

730
        // We'll also handle the case of the remote party broadcasting
731
        // their commitment transaction which is one height above ours.
732
        // This case can arise when we initiate a state transition, but
733
        // the remote party has a fail crash _after_ accepting the new
734
        // state, but _before_ sending their signature to us.
735
        case chainSet.remotePendingCommit != nil &&
736
                chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
1✔
737

1✔
738
                log.Infof("Remote party broadcast pending set, "+
1✔
739
                        "commit_num=%v", chainSet.remoteStateNum+1)
1✔
740

1✔
741
                chainSet.commitSet.ConfCommitKey = fn.Some(RemotePendingHtlcSet)
1✔
742
                err := c.dispatchRemoteForceClose(
1✔
743
                        commitSpend, *chainSet.remotePendingCommit,
1✔
744
                        chainSet.commitSet,
1✔
745
                        c.cfg.chanState.RemoteNextRevocation,
1✔
746
                )
1✔
747
                if err != nil {
1✔
748
                        return false, fmt.Errorf("unable to handle remote "+
×
749
                                "close for chan_point=%v: %v",
×
750
                                c.cfg.chanState.FundingOutpoint, err)
×
751
                }
×
752

753
                return true, nil
1✔
754
        }
755

756
        // This is neither a remote force close or a "future" commitment, we
757
        // now check whether it's a remote breach and properly handle it.
758
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
11✔
759
}
760

761
// handlePossibleBreach checks whether the remote has breached and dispatches a
762
// breach resolution to claim funds.
763
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
764
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
11✔
765

11✔
766
        // We check if we have a revoked state at this state num that matches
11✔
767
        // the spend transaction.
11✔
768
        spendHeight := uint32(commitSpend.SpendingHeight)
11✔
769
        retribution, err := lnwallet.NewBreachRetribution(
11✔
770
                c.cfg.chanState, broadcastStateNum, spendHeight,
11✔
771
                commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
11✔
772
        )
11✔
773

11✔
774
        switch {
11✔
775
        // If we had no log entry at this height, this was not a revoked state.
776
        case err == channeldb.ErrLogEntryNotFound:
8✔
777
                return false, nil
8✔
778
        case err == channeldb.ErrNoPastDeltas:
3✔
779
                return false, nil
3✔
780

781
        case err != nil:
×
782
                return false, fmt.Errorf("unable to create breach "+
×
783
                        "retribution: %v", err)
×
784
        }
785

786
        // We found a revoked state at this height, but it could still be our
787
        // own broadcasted state we are looking at. Therefore check that the
788
        // commit matches before assuming it was a breach.
789
        commitHash := commitSpend.SpendingTx.TxHash()
×
790
        if retribution.BreachTxHash != commitHash {
×
791
                return false, nil
×
792
        }
×
793

794
        // Create an AnchorResolution for the breached state.
795
        anchorRes, err := lnwallet.NewAnchorResolution(
×
796
                c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
×
797
                lntypes.Remote,
×
798
        )
×
799
        if err != nil {
×
800
                return false, fmt.Errorf("unable to create anchor "+
×
801
                        "resolution: %v", err)
×
802
        }
×
803

804
        // We'll set the ConfCommitKey here as the remote htlc set. This is
805
        // only used to ensure a nil-pointer-dereference doesn't occur and is
806
        // not used otherwise. The HTLC's may not exist for the
807
        // RemotePendingHtlcSet.
808
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
×
809

×
810
        // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
×
811
        // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
×
812
        // broadcast to allow subscribers to swiftly dispatch justice!!!
×
813
        err = c.dispatchContractBreach(
×
814
                commitSpend, chainSet, broadcastStateNum, retribution,
×
815
                anchorRes,
×
816
        )
×
817
        if err != nil {
×
818
                return false, fmt.Errorf("unable to handle channel "+
×
819
                        "breach for chan_point=%v: %v",
×
820
                        c.cfg.chanState.FundingOutpoint, err)
×
821
        }
×
822

823
        return true, nil
×
824
}
825

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

4✔
835
        log.Warnf("Remote node broadcast state #%v, "+
4✔
836
                "which is more than 1 beyond best known "+
4✔
837
                "state #%v!!! Attempting recovery...",
4✔
838
                broadcastStateNum, chainSet.remoteStateNum)
4✔
839

4✔
840
        // If this isn't a tweakless commitment, then we'll need to wait for
4✔
841
        // the remote party's latest unrevoked commitment point to be presented
4✔
842
        // to us as we need this to sweep. Otherwise, we can dispatch the
4✔
843
        // remote close and sweep immediately using a fake commitPoint as it
4✔
844
        // isn't actually needed for recovery anymore.
4✔
845
        commitPoint := c.cfg.chanState.RemoteCurrentRevocation
4✔
846
        tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
4✔
847
        if !tweaklessCommit {
8✔
848
                commitPoint = c.waitForCommitmentPoint()
4✔
849
                if commitPoint == nil {
4✔
850
                        return false, fmt.Errorf("unable to get commit point")
×
851
                }
×
852

853
                log.Infof("Recovered commit point(%x) for "+
4✔
854
                        "channel(%v)! Now attempting to use it to "+
4✔
855
                        "sweep our funds...",
4✔
856
                        commitPoint.SerializeCompressed(),
4✔
857
                        c.cfg.chanState.FundingOutpoint)
4✔
858
        } else {
×
859
                log.Infof("ChannelPoint(%v) is tweakless, "+
×
860
                        "moving to sweep directly on chain",
×
861
                        c.cfg.chanState.FundingOutpoint)
×
862
        }
×
863

864
        // Since we don't have the commitment stored for this state, we'll just
865
        // pass an empty commitment within the commitment set. Note that this
866
        // means we won't be able to recover any HTLC funds.
867
        //
868
        // TODO(halseth): can we try to recover some HTLCs?
869
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
4✔
870
        err := c.dispatchRemoteForceClose(
4✔
871
                commitSpend, channeldb.ChannelCommitment{},
4✔
872
                chainSet.commitSet, commitPoint,
4✔
873
        )
4✔
874
        if err != nil {
4✔
875
                return false, fmt.Errorf("unable to handle remote "+
×
876
                        "close for chan_point=%v: %v",
×
877
                        c.cfg.chanState.FundingOutpoint, err)
×
878
        }
×
879

880
        return true, nil
4✔
881
}
882

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

900
        // Here we define a function capable of identifying whether an output
901
        // corresponds with our local delivery script from a ShutdownInfo if we
902
        // have a ShutdownInfo for this chainWatcher's underlying channel.
903
        //
904
        // isDeliveryOutput :: *TxOut -> bool
905
        isDeliveryOutput := func(o *wire.TxOut) bool {
×
906
                return fn.ElimOption(
×
907
                        oDeliveryAddr,
×
908
                        // If we don't have a delivery addr, then the output
×
909
                        // can't match it.
×
910
                        func() bool { return false },
×
911
                        // Otherwise if the PkScript of the TxOut matches our
912
                        // delivery script then this is a delivery output.
913
                        func(a lnwire.DeliveryAddress) bool {
×
914
                                return slices.Equal(a, o.PkScript)
×
915
                        },
×
916
                )
917
        }
918

919
        // Here we define a function capable of identifying whether an output
920
        // belongs to the LND wallet. We use this as a heuristic in the case
921
        // where we might be looking for spendable force closure outputs.
922
        //
923
        // isWalletOutput :: *TxOut -> bool
924
        isWalletOutput := func(out *wire.TxOut) bool {
×
925
                _, addrs, _, err := txscript.ExtractPkScriptAddrs(
×
926
                        // Doesn't matter what net we actually pass in.
×
927
                        out.PkScript, &chaincfg.TestNet3Params,
×
928
                )
×
929
                if err != nil {
×
930
                        return false
×
931
                }
×
932

933
                return fn.Any(c.cfg.isOurAddr, addrs)
×
934
        }
935

936
        // Grab all of the outputs that correspond with our delivery address
937
        // or our wallet is aware of.
938
        outs := fn.Filter(fn.PredOr(isDeliveryOutput, isWalletOutput), tx.TxOut)
×
939

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

943
        // Return the sum.
944
        return btcutil.Amount(fn.Sum(vals))
×
945
}
946

947
// dispatchCooperativeClose processed a detect cooperative channel closure.
948
// We'll use the spending transaction to locate our output within the
949
// transaction, then clean up the database state. We'll also dispatch a
950
// notification to all subscribers that the channel has been closed in this
951
// manner.
952
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
×
953
        broadcastTx := commitSpend.SpendingTx
×
954

×
955
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
×
956
                c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
×
957

×
958
        // If the input *is* final, then we'll check to see which output is
×
959
        // ours.
×
960
        localAmt := c.toSelfAmount(broadcastTx)
×
961

×
962
        // Once this is known, we'll mark the state as fully closed in the
×
963
        // database. We can do this as a cooperatively closed channel has all
×
964
        // its outputs resolved after only one confirmation.
×
965
        closeSummary := &channeldb.ChannelCloseSummary{
×
966
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
×
967
                ChainHash:               c.cfg.chanState.ChainHash,
×
968
                ClosingTXID:             *commitSpend.SpenderTxHash,
×
969
                RemotePub:               c.cfg.chanState.IdentityPub,
×
970
                Capacity:                c.cfg.chanState.Capacity,
×
971
                CloseHeight:             uint32(commitSpend.SpendingHeight),
×
972
                SettledBalance:          localAmt,
×
973
                CloseType:               channeldb.CooperativeClose,
×
974
                ShortChanID:             c.cfg.chanState.ShortChanID(),
×
975
                IsPending:               true,
×
976
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
×
977
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
×
978
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
979
        }
×
980

×
981
        // Attempt to add a channel sync message to the close summary.
×
982
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
×
983
        if err != nil {
×
984
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
985
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
986
        } else {
×
987
                closeSummary.LastChanSyncMsg = chanSync
×
988
        }
×
989

990
        // Create a summary of all the information needed to handle the
991
        // cooperative closure.
992
        closeInfo := &CooperativeCloseInfo{
×
993
                ChannelCloseSummary: closeSummary,
×
994
        }
×
995

×
996
        // With the event processed, we'll now notify all subscribers of the
×
997
        // event.
×
998
        c.Lock()
×
999
        for _, sub := range c.clientSubscriptions {
×
1000
                select {
×
1001
                case sub.CooperativeClosure <- closeInfo:
×
1002
                case <-c.quit:
×
1003
                        c.Unlock()
×
1004
                        return fmt.Errorf("exiting")
×
1005
                }
1006
        }
1007
        c.Unlock()
×
1008

×
1009
        return nil
×
1010
}
1011

1012
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
1013
func (c *chainWatcher) dispatchLocalForceClose(
1014
        commitSpend *chainntnfs.SpendDetail,
1015
        stateNum uint64, commitSet CommitSet) error {
9✔
1016

9✔
1017
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
9✔
1018
                "detected", c.cfg.chanState.FundingOutpoint)
9✔
1019

9✔
1020
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
9✔
1021
                c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
9✔
1022
                c.cfg.auxLeafStore, c.cfg.auxResolver,
9✔
1023
        )
9✔
1024
        if err != nil {
9✔
1025
                return err
×
1026
        }
×
1027

1028
        // As we've detected that the channel has been closed, immediately
1029
        // creating a close summary for future usage by related sub-systems.
1030
        chanSnapshot := forceClose.ChanSnapshot
9✔
1031
        closeSummary := &channeldb.ChannelCloseSummary{
9✔
1032
                ChanPoint:               chanSnapshot.ChannelPoint,
9✔
1033
                ChainHash:               chanSnapshot.ChainHash,
9✔
1034
                ClosingTXID:             forceClose.CloseTx.TxHash(),
9✔
1035
                RemotePub:               &chanSnapshot.RemoteIdentity,
9✔
1036
                Capacity:                chanSnapshot.Capacity,
9✔
1037
                CloseType:               channeldb.LocalForceClose,
9✔
1038
                IsPending:               true,
9✔
1039
                ShortChanID:             c.cfg.chanState.ShortChanID(),
9✔
1040
                CloseHeight:             uint32(commitSpend.SpendingHeight),
9✔
1041
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
9✔
1042
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
9✔
1043
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
9✔
1044
        }
9✔
1045

9✔
1046
        resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
9✔
1047
                fmt.Errorf("resolutions not found"),
9✔
1048
        )
9✔
1049
        if err != nil {
9✔
1050
                return err
×
1051
        }
×
1052

1053
        // If our commitment output isn't dust or we have active HTLC's on the
1054
        // commitment transaction, then we'll populate the balances on the
1055
        // close channel summary.
1056
        if resolutions.CommitResolution != nil {
15✔
1057
                localBalance := chanSnapshot.LocalBalance.ToSatoshis()
6✔
1058
                closeSummary.SettledBalance = localBalance
6✔
1059
                closeSummary.TimeLockedBalance = localBalance
6✔
1060
        }
6✔
1061

1062
        if resolutions.HtlcResolutions != nil {
18✔
1063
                for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
9✔
1064
                        htlcValue := btcutil.Amount(
×
1065
                                htlc.SweepSignDesc.Output.Value,
×
1066
                        )
×
1067
                        closeSummary.TimeLockedBalance += htlcValue
×
1068
                }
×
1069
        }
1070

1071
        // Attempt to add a channel sync message to the close summary.
1072
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
9✔
1073
        if err != nil {
9✔
1074
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1075
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1076
        } else {
9✔
1077
                closeSummary.LastChanSyncMsg = chanSync
9✔
1078
        }
9✔
1079

1080
        // With the event processed, we'll now notify all subscribers of the
1081
        // event.
1082
        closeInfo := &LocalUnilateralCloseInfo{
9✔
1083
                SpendDetail:            commitSpend,
9✔
1084
                LocalForceCloseSummary: forceClose,
9✔
1085
                ChannelCloseSummary:    closeSummary,
9✔
1086
                CommitSet:              commitSet,
9✔
1087
        }
9✔
1088
        c.Lock()
9✔
1089
        for _, sub := range c.clientSubscriptions {
18✔
1090
                select {
9✔
1091
                case sub.LocalUnilateralClosure <- closeInfo:
9✔
1092
                case <-c.quit:
×
1093
                        c.Unlock()
×
1094
                        return fmt.Errorf("exiting")
×
1095
                }
1096
        }
1097
        c.Unlock()
9✔
1098

9✔
1099
        return nil
9✔
1100
}
1101

1102
// dispatchRemoteForceClose processes a detected unilateral channel closure by
1103
// the remote party. This function will prepare a UnilateralCloseSummary which
1104
// will then be sent to any subscribers allowing them to resolve all our funds
1105
// in the channel on chain. Once this close summary is prepared, all registered
1106
// subscribers will receive a notification of this event. The commitPoint
1107
// argument should be set to the per_commitment_point corresponding to the
1108
// spending commitment.
1109
//
1110
// NOTE: The remoteCommit argument should be set to the stored commitment for
1111
// this particular state. If we don't have the commitment stored (should only
1112
// happen in case we have lost state) it should be set to an empty struct, in
1113
// which case we will attempt to sweep the non-HTLC output using the passed
1114
// commitPoint.
1115
func (c *chainWatcher) dispatchRemoteForceClose(
1116
        commitSpend *chainntnfs.SpendDetail,
1117
        remoteCommit channeldb.ChannelCommitment,
1118
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
6✔
1119

6✔
1120
        log.Infof("Unilateral close of ChannelPoint(%v) "+
6✔
1121
                "detected", c.cfg.chanState.FundingOutpoint)
6✔
1122

6✔
1123
        // First, we'll create a closure summary that contains all the
6✔
1124
        // materials required to let each subscriber sweep the funds in the
6✔
1125
        // channel on-chain.
6✔
1126
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
6✔
1127
                c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
6✔
1128
                commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
6✔
1129
        )
6✔
1130
        if err != nil {
6✔
1131
                return err
×
1132
        }
×
1133

1134
        // With the event processed, we'll now notify all subscribers of the
1135
        // event.
1136
        c.Lock()
6✔
1137
        for _, sub := range c.clientSubscriptions {
12✔
1138
                select {
6✔
1139
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
1140
                        UnilateralCloseSummary: uniClose,
1141
                        CommitSet:              commitSet,
1142
                }:
6✔
1143
                case <-c.quit:
×
1144
                        c.Unlock()
×
1145
                        return fmt.Errorf("exiting")
×
1146
                }
1147
        }
1148
        c.Unlock()
6✔
1149

6✔
1150
        return nil
6✔
1151
}
1152

1153
// dispatchContractBreach processes a detected contract breached by the remote
1154
// party. This method is to be called once we detect that the remote party has
1155
// broadcast a prior revoked commitment state. This method well prepare all the
1156
// materials required to bring the cheater to justice, then notify all
1157
// registered subscribers of this event.
1158
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
1159
        chainSet *chainSet, broadcastStateNum uint64,
1160
        retribution *lnwallet.BreachRetribution,
1161
        anchorRes *lnwallet.AnchorResolution) error {
×
1162

×
1163
        log.Warnf("Remote peer has breached the channel contract for "+
×
1164
                "ChannelPoint(%v). Revoked state #%v was broadcast!!!",
×
1165
                c.cfg.chanState.FundingOutpoint, broadcastStateNum)
×
1166

×
1167
        if err := c.cfg.chanState.MarkBorked(); err != nil {
×
1168
                return fmt.Errorf("unable to mark channel as borked: %w", err)
×
1169
        }
×
1170

1171
        spendHeight := uint32(spendEvent.SpendingHeight)
×
1172

×
1173
        log.Debugf("Punishment breach retribution created: %v",
×
1174
                lnutils.NewLogClosure(func() string {
×
1175
                        retribution.KeyRing.LocalHtlcKey = nil
×
1176
                        retribution.KeyRing.RemoteHtlcKey = nil
×
1177
                        retribution.KeyRing.ToLocalKey = nil
×
1178
                        retribution.KeyRing.ToRemoteKey = nil
×
1179
                        retribution.KeyRing.RevocationKey = nil
×
1180
                        return spew.Sdump(retribution)
×
1181
                }))
×
1182

1183
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
×
1184
        closeSummary := channeldb.ChannelCloseSummary{
×
1185
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
×
1186
                ChainHash:               c.cfg.chanState.ChainHash,
×
1187
                ClosingTXID:             *spendEvent.SpenderTxHash,
×
1188
                CloseHeight:             spendHeight,
×
1189
                RemotePub:               c.cfg.chanState.IdentityPub,
×
1190
                Capacity:                c.cfg.chanState.Capacity,
×
1191
                SettledBalance:          settledBalance,
×
1192
                CloseType:               channeldb.BreachClose,
×
1193
                IsPending:               true,
×
1194
                ShortChanID:             c.cfg.chanState.ShortChanID(),
×
1195
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
×
1196
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
×
1197
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
1198
        }
×
1199

×
1200
        // Attempt to add a channel sync message to the close summary.
×
1201
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
×
1202
        if err != nil {
×
1203
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1204
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1205
        } else {
×
1206
                closeSummary.LastChanSyncMsg = chanSync
×
1207
        }
×
1208

1209
        // Hand the retribution info over to the BreachArbitrator. This function
1210
        // will wait for a response from the breach arbiter and then proceed to
1211
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1212
        // will then mark the channel as closed after resolutions and the
1213
        // commit set are logged in the arbitrator log.
1214
        if err := c.cfg.contractBreach(retribution); err != nil {
×
1215
                log.Errorf("unable to hand breached contract off to "+
×
1216
                        "BreachArbitrator: %v", err)
×
1217
                return err
×
1218
        }
×
1219

1220
        breachRes := &BreachResolution{
×
1221
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
×
1222
        }
×
1223

×
1224
        breachInfo := &BreachCloseInfo{
×
1225
                CommitHash:       spendEvent.SpendingTx.TxHash(),
×
1226
                BreachResolution: breachRes,
×
1227
                AnchorResolution: anchorRes,
×
1228
                CommitSet:        chainSet.commitSet,
×
1229
                CloseSummary:     closeSummary,
×
1230
        }
×
1231

×
1232
        // With the event processed and channel closed, we'll now notify all
×
1233
        // subscribers of the event.
×
1234
        c.Lock()
×
1235
        for _, sub := range c.clientSubscriptions {
×
1236
                select {
×
1237
                case sub.ContractBreach <- breachInfo:
×
1238
                case <-c.quit:
×
1239
                        c.Unlock()
×
1240
                        return fmt.Errorf("quitting")
×
1241
                }
1242
        }
1243
        c.Unlock()
×
1244

×
1245
        return nil
×
1246
}
1247

1248
// waitForCommitmentPoint waits for the commitment point to be inserted into
1249
// the local database. We'll use this method in the DLP case, to wait for the
1250
// remote party to send us their point, as we can't proceed until we have that.
1251
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
4✔
1252
        // If we are lucky, the remote peer sent us the correct commitment
4✔
1253
        // point during channel sync, such that we can sweep our funds. If we
4✔
1254
        // cannot find the commit point, there's not much we can do other than
4✔
1255
        // wait for us to retrieve it. We will attempt to retrieve it from the
4✔
1256
        // peer each time we connect to it.
4✔
1257
        //
4✔
1258
        // TODO(halseth): actively initiate re-connection to the peer?
4✔
1259
        backoff := minCommitPointPollTimeout
4✔
1260
        for {
8✔
1261
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
4✔
1262
                if err == nil {
8✔
1263
                        return commitPoint
4✔
1264
                }
4✔
1265

1266
                log.Errorf("Unable to retrieve commitment point for "+
×
1267
                        "channel(%v) with lost state: %v. Retrying in %v.",
×
1268
                        c.cfg.chanState.FundingOutpoint, err, backoff)
×
1269

×
1270
                select {
×
1271
                // Wait before retrying, with an exponential backoff.
1272
                case <-time.After(backoff):
×
1273
                        backoff = 2 * backoff
×
1274
                        if backoff > maxCommitPointPollTimeout {
×
1275
                                backoff = maxCommitPointPollTimeout
×
1276
                        }
×
1277

1278
                case <-c.quit:
×
1279
                        return nil
×
1280
                }
1281
        }
1282
}
1283

1284
// deriveFundingPkScript derives the script used in the funding output.
1285
func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) {
26✔
1286
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
26✔
1287
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
26✔
1288

26✔
1289
        var (
26✔
1290
                err             error
26✔
1291
                fundingPkScript []byte
26✔
1292
        )
26✔
1293

26✔
1294
        if chanState.ChanType.IsTaproot() {
26✔
1295
                fundingPkScript, _, err = input.GenTaprootFundingScript(
×
1296
                        localKey, remoteKey, 0, chanState.TapscriptRoot,
×
1297
                )
×
1298
                if err != nil {
×
1299
                        return nil, err
×
1300
                }
×
1301
        } else {
26✔
1302
                multiSigScript, err := input.GenMultiSigScript(
26✔
1303
                        localKey.SerializeCompressed(),
26✔
1304
                        remoteKey.SerializeCompressed(),
26✔
1305
                )
26✔
1306
                if err != nil {
26✔
1307
                        return nil, err
×
1308
                }
×
1309
                fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
26✔
1310
                if err != nil {
26✔
1311
                        return nil, err
×
1312
                }
×
1313
        }
1314

1315
        return fundingPkScript, nil
26✔
1316
}
1317

1318
// deriveHeightHint derives the block height for the channel opening.
1319
func deriveHeightHint(chanState *channeldb.OpenChannel) uint32 {
26✔
1320
        // As a height hint, we'll try to use the opening height, but if the
26✔
1321
        // channel isn't yet open, then we'll use the height it was broadcast
26✔
1322
        // at. This may be an unconfirmed zero-conf channel.
26✔
1323
        heightHint := chanState.ShortChanID().BlockHeight
26✔
1324
        if heightHint == 0 {
26✔
1325
                heightHint = chanState.BroadcastHeight()
×
1326
        }
×
1327

1328
        // Since no zero-conf state is stored in a channel backup, the below
1329
        // logic will not be triggered for restored, zero-conf channels. Set
1330
        // the height hint for zero-conf channels.
1331
        if chanState.IsZeroConf() {
26✔
1332
                if chanState.ZeroConfConfirmed() {
×
1333
                        // If the zero-conf channel is confirmed, we'll use the
×
1334
                        // confirmed SCID's block height.
×
1335
                        heightHint = chanState.ZeroConfRealScid().BlockHeight
×
1336
                } else {
×
1337
                        // The zero-conf channel is unconfirmed. We'll need to
×
1338
                        // use the FundingBroadcastHeight.
×
1339
                        heightHint = chanState.BroadcastHeight()
×
1340
                }
×
1341
        }
1342

1343
        return heightHint
26✔
1344
}
1345

1346
// handleCommitSpend takes a spending tx of the funding output and handles the
1347
// channel close based on the closure type.
1348
func (c *chainWatcher) handleCommitSpend(
1349
        commitSpend *chainntnfs.SpendDetail) error {
15✔
1350

15✔
1351
        commitTxBroadcast := commitSpend.SpendingTx
15✔
1352

15✔
1353
        // First, we'll construct the chainset which includes all the data we
15✔
1354
        // need to dispatch an event to our subscribers about this possible
15✔
1355
        // channel close event.
15✔
1356
        chainSet, err := newChainSet(c.cfg.chanState)
15✔
1357
        if err != nil {
15✔
1358
                return fmt.Errorf("create commit set: %w", err)
×
1359
        }
×
1360

1361
        // Decode the state hint encoded within the commitment transaction to
1362
        // determine if this is a revoked state or not.
1363
        obfuscator := c.stateHintObfuscator
15✔
1364
        broadcastStateNum := c.cfg.extractStateNumHint(
15✔
1365
                commitTxBroadcast, obfuscator,
15✔
1366
        )
15✔
1367

15✔
1368
        // We'll go on to check whether it could be our own commitment that was
15✔
1369
        // published and know is confirmed.
15✔
1370
        ok, err := c.handleKnownLocalState(
15✔
1371
                commitSpend, broadcastStateNum, chainSet,
15✔
1372
        )
15✔
1373
        if err != nil {
15✔
1374
                return fmt.Errorf("handle known local state: %w", err)
×
1375
        }
×
1376
        if ok {
17✔
1377
                return nil
2✔
1378
        }
2✔
1379

1380
        // Now that we know it is neither a non-cooperative closure nor a local
1381
        // close with the latest state, we check if it is the remote that
1382
        // closed with any prior or current state.
1383
        ok, err = c.handleKnownRemoteState(
13✔
1384
                commitSpend, broadcastStateNum, chainSet,
13✔
1385
        )
13✔
1386
        if err != nil {
13✔
1387
                return fmt.Errorf("handle known remote state: %w", err)
×
1388
        }
×
1389
        if ok {
15✔
1390
                return nil
2✔
1391
        }
2✔
1392

1393
        // Next, we'll check to see if this is a cooperative channel closure or
1394
        // not. This is characterized by having an input sequence number that's
1395
        // finalized. This won't happen with regular commitment transactions
1396
        // due to the state hint encoding scheme.
1397
        switch commitTxBroadcast.TxIn[0].Sequence {
11✔
1398
        case wire.MaxTxInSequenceNum:
×
1399
                fallthrough
×
1400
        case mempool.MaxRBFSequence:
×
1401
                // TODO(roasbeef): rare but possible, need itest case for
×
1402
                err := c.dispatchCooperativeClose(commitSpend)
×
1403
                if err != nil {
×
1404
                        return fmt.Errorf("handle coop close: %w", err)
×
1405
                }
×
1406

1407
                return nil
×
1408
        }
1409

1410
        log.Warnf("Unknown commitment broadcast for ChannelPoint(%v) ",
11✔
1411
                c.cfg.chanState.FundingOutpoint)
11✔
1412

11✔
1413
        // We'll try to recover as best as possible from losing state.  We
11✔
1414
        // first check if this was a local unknown state. This could happen if
11✔
1415
        // we force close, then lose state or attempt recovery before the
11✔
1416
        // commitment confirms.
11✔
1417
        ok, err = c.handleUnknownLocalState(
11✔
1418
                commitSpend, broadcastStateNum, chainSet,
11✔
1419
        )
11✔
1420
        if err != nil {
11✔
1421
                return fmt.Errorf("handle known local state: %w", err)
×
1422
        }
×
1423
        if ok {
18✔
1424
                return nil
7✔
1425
        }
7✔
1426

1427
        // Since it was neither a known remote state, nor a local state that
1428
        // was published, it most likely mean we lost state and the remote node
1429
        // closed. In this case we must start the DLP protocol in hope of
1430
        // getting our money back.
1431
        ok, err = c.handleUnknownRemoteState(
4✔
1432
                commitSpend, broadcastStateNum, chainSet,
4✔
1433
        )
4✔
1434
        if err != nil {
4✔
1435
                return fmt.Errorf("handle unknown remote state: %w", err)
×
1436
        }
×
1437
        if ok {
8✔
1438
                return nil
4✔
1439
        }
4✔
1440

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

×
1444
        return nil
×
1445
}
1446

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

UNCOV
1466
                log.Debugf("Found spend details for funding output: %v",
×
UNCOV
1467
                        spend.SpenderTxHash)
×
UNCOV
1468

×
UNCOV
1469
                return spend
×
1470

1471
        default:
15✔
1472
        }
1473

1474
        return nil
15✔
1475
}
1476

1477
// chanPointConfirmed checks whether the given channel point has confirmed.
1478
// This is used to ensure that the funding output has confirmed on chain before
1479
// we proceed with the rest of the close observer logic for taproot channels.
1480
func (c *chainWatcher) chanPointConfirmed() bool {
×
1481
        op := c.cfg.chanState.FundingOutpoint
×
1482

×
1483
        select {
×
1484
        case _, ok := <-c.fundingConfirmedNtfn.Confirmed:
×
1485
                // If the channel was closed, then this means that the notifier
×
1486
                // exited, so we will as well.
×
1487
                if !ok {
×
1488
                        return false
×
1489
                }
×
1490

1491
                log.Debugf("Taproot ChannelPoint(%v) confirmed", op)
×
1492

×
1493
                // The channel point has confirmed on chain. We now cancel the
×
1494
                // subscription.
×
1495
                c.fundingConfirmedNtfn.Cancel()
×
1496

×
1497
                return true
×
1498

1499
        default:
×
1500
                log.Infof("Taproot ChannelPoint(%v) not confirmed yet", op)
×
1501

×
1502
                return false
×
1503
        }
1504
}
1505

1506
// handleBlockbeat takes a blockbeat and queries for a spending tx for the
1507
// funding output. If the spending tx is found, it will be handled based on the
1508
// closure type.
1509
func (c *chainWatcher) handleBlockbeat(beat chainio.Blockbeat) {
15✔
1510
        // Notify the chain watcher has processed the block.
15✔
1511
        defer c.NotifyBlockProcessed(beat, nil)
15✔
1512

15✔
1513
        // If we have a fundingConfirmedNtfn, it means this is a taproot
15✔
1514
        // channel that is pending, before we proceed, we want to ensure that
15✔
1515
        // the expected funding output has confirmed on chain.
15✔
1516
        if c.fundingConfirmedNtfn != nil {
15✔
1517
                // If the funding output hasn't confirmed in this block, we
×
1518
                // will check it again in the next block.
×
1519
                if !c.chanPointConfirmed() {
×
1520
                        return
×
1521
                }
×
1522
        }
1523

1524
        // Perform a non-blocking read to check whether the funding output was
1525
        // spent.
1526
        spend := c.checkFundingSpend()
15✔
1527
        if spend == nil {
30✔
1528
                log.Tracef("No spend found for ChannelPoint(%v) in block %v",
15✔
1529
                        c.cfg.chanState.FundingOutpoint, beat.Height())
15✔
1530

15✔
1531
                return
15✔
1532
        }
15✔
1533

1534
        // The funding output was spent, we now handle it by sending a close
1535
        // event to the channel arbitrator.
UNCOV
1536
        err := c.handleCommitSpend(spend)
×
UNCOV
1537
        if err != nil {
×
1538
                log.Errorf("Failed to handle commit spend: %v", err)
×
1539
        }
×
1540
}
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