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

lightningnetwork / lnd / 14358372723

09 Apr 2025 01:26PM UTC coverage: 56.696% (-12.3%) from 69.037%
14358372723

Pull #9696

github

web-flow
Merge e2837e400 into 867d27d68
Pull Request #9696: Add `development_guidelines.md` for both human and machine

107055 of 188823 relevant lines covered (56.7%)

22721.56 hits per line

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

57.02
/contractcourt/chain_watcher.go
1
package contractcourt
2

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

119
        return true
8✔
120
}
121

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

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

131
        return htlcSets
14✔
132
}
133

134
// String return a human-readable representation of the CommitSet.
135
func (c *CommitSet) String() string {
×
136
        if c == nil {
×
137
                return "nil"
×
138
        }
×
139

140
        // Create a descriptive string for the ConfCommitKey.
141
        commitKey := "none"
×
142
        c.ConfCommitKey.WhenSome(func(k HtlcSetKey) {
×
143
                commitKey = k.String()
×
144
        })
×
145

146
        // Create a map to hold all the htlcs.
147
        htlcSet := make(map[string]string)
×
148
        for k, htlcs := range c.HtlcSets {
×
149
                // Create a map for this particular set.
×
150
                desc := make([]string, len(htlcs))
×
151
                for i, htlc := range htlcs {
×
152
                        desc[i] = fmt.Sprintf("%x", htlc.RHash)
×
153
                }
×
154

155
                // Add the description to the set key.
156
                htlcSet[k.String()] = fmt.Sprintf("count: %v, htlcs=%v",
×
157
                        len(htlcs), desc)
×
158
        }
159

160
        return fmt.Sprintf("ConfCommitKey=%v, HtlcSets=%v", commitKey, htlcSet)
×
161
}
162

163
// ChainEventSubscription is a struct that houses a subscription to be notified
164
// for any on-chain events related to a channel. There are three types of
165
// possible on-chain events: a cooperative channel closure, a unilateral
166
// channel closure, and a channel breach. The fourth type: a force close is
167
// locally initiated, so we don't provide any event stream for said event.
168
type ChainEventSubscription struct {
169
        // ChanPoint is that channel that chain events will be dispatched for.
170
        ChanPoint wire.OutPoint
171

172
        // RemoteUnilateralClosure is a channel that will be sent upon in the
173
        // event that the remote party's commitment transaction is confirmed.
174
        RemoteUnilateralClosure chan *RemoteUnilateralCloseInfo
175

176
        // LocalUnilateralClosure is a channel that will be sent upon in the
177
        // event that our commitment transaction is confirmed.
178
        LocalUnilateralClosure chan *LocalUnilateralCloseInfo
179

180
        // CooperativeClosure is a signal that will be sent upon once a
181
        // cooperative channel closure has been detected confirmed.
182
        CooperativeClosure chan *CooperativeCloseInfo
183

184
        // ContractBreach is a channel that will be sent upon if we detect a
185
        // contract breach. The struct sent across the channel contains all the
186
        // material required to bring the cheating channel peer to justice.
187
        ContractBreach chan *BreachCloseInfo
188

189
        // Cancel cancels the subscription to the event stream for a particular
190
        // channel. This method should be called once the caller no longer needs to
191
        // be notified of any on-chain events for a particular channel.
192
        Cancel func()
193
}
194

195
// chainWatcherConfig encapsulates all the necessary functions and interfaces
196
// needed to watch and act on on-chain events for a particular channel.
197
type chainWatcherConfig struct {
198
        // chanState is a snapshot of the persistent state of the channel that
199
        // we're watching. In the event of an on-chain event, we'll query the
200
        // database to ensure that we act using the most up to date state.
201
        chanState *channeldb.OpenChannel
202

203
        // notifier is a reference to the channel notifier that we'll use to be
204
        // notified of output spends and when transactions are confirmed.
205
        notifier chainntnfs.ChainNotifier
206

207
        // signer is the main signer instances that will be responsible for
208
        // signing any HTLC and commitment transaction generated by the state
209
        // machine.
210
        signer input.Signer
211

212
        // contractBreach is a method that will be called by the watcher if it
213
        // detects that a contract breach transaction has been confirmed. It
214
        // will only return a non-nil error when the BreachArbitrator has
215
        // preserved the necessary breach info for this channel point.
216
        contractBreach func(*lnwallet.BreachRetribution) error
217

218
        // isOurAddr is a function that returns true if the passed address is
219
        // known to us.
220
        isOurAddr func(btcutil.Address) bool
221

222
        // extractStateNumHint extracts the encoded state hint using the passed
223
        // obfuscater. This is used by the chain watcher to identify which
224
        // state was broadcast and confirmed on-chain.
225
        extractStateNumHint func(*wire.MsgTx, [lnwallet.StateHintSize]byte) uint64
226

227
        // auxLeafStore can be used to fetch information for custom channels.
228
        auxLeafStore fn.Option[lnwallet.AuxLeafStore]
229

230
        // auxResolver is used to supplement contract resolution.
231
        auxResolver fn.Option[lnwallet.AuxContractResolver]
232
}
233

234
// chainWatcher is a system that's assigned to every active channel. The duty
235
// of this system is to watch the chain for spends of the channels chan point.
236
// If a spend is detected then with chain watcher will notify all subscribers
237
// that the channel has been closed, and also give them the materials necessary
238
// to sweep the funds of the channel on chain eventually.
239
type chainWatcher struct {
240
        started int32 // To be used atomically.
241
        stopped int32 // To be used atomically.
242

243
        // Embed the blockbeat consumer struct to get access to the method
244
        // `NotifyBlockProcessed` and the `BlockbeatChan`.
245
        chainio.BeatConsumer
246

247
        quit chan struct{}
248
        wg   sync.WaitGroup
249

250
        cfg chainWatcherConfig
251

252
        // stateHintObfuscator is a 48-bit state hint that's used to obfuscate
253
        // the current state number on the commitment transactions.
254
        stateHintObfuscator [lnwallet.StateHintSize]byte
255

256
        // All the fields below are protected by this mutex.
257
        sync.Mutex
258

259
        // clientID is an ephemeral counter used to keep track of each
260
        // individual client subscription.
261
        clientID uint64
262

263
        // clientSubscriptions is a map that keeps track of all the active
264
        // client subscriptions for events related to this channel.
265
        clientSubscriptions map[uint64]*ChainEventSubscription
266

267
        // fundingSpendNtfn is the spending notification subscription for the
268
        // funding outpoint.
269
        fundingSpendNtfn *chainntnfs.SpendEvent
270

271
        // fundingConfirmedNtfn is the confirmation notification subscription
272
        // for the funding outpoint. This is only created if the channel is
273
        // both taproot and pending confirmation.
274
        //
275
        // For taproot pkscripts, `RegisterSpendNtfn` will only notify on the
276
        // outpoint being spent and not the outpoint+pkscript due to
277
        // `ComputePkScript` being unable to compute the pkscript if a key
278
        // spend is used. We need to add a `RegisterConfirmationsNtfn` here to
279
        // ensure that the outpoint+pkscript pair is confirmed before calling
280
        // `RegisterSpendNtfn`.
281
        fundingConfirmedNtfn *chainntnfs.ConfirmationEvent
282
}
283

284
// newChainWatcher returns a new instance of a chainWatcher for a channel given
285
// the chan point to watch, and also a notifier instance that will allow us to
286
// detect on chain events.
287
func newChainWatcher(cfg chainWatcherConfig) (*chainWatcher, error) {
26✔
288
        // In order to be able to detect the nature of a potential channel
26✔
289
        // closure we'll need to reconstruct the state hint bytes used to
26✔
290
        // obfuscate the commitment state number encoded in the lock time and
26✔
291
        // sequence fields.
26✔
292
        var stateHint [lnwallet.StateHintSize]byte
26✔
293
        chanState := cfg.chanState
26✔
294
        if chanState.IsInitiator {
52✔
295
                stateHint = lnwallet.DeriveStateHintObfuscator(
26✔
296
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
26✔
297
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
26✔
298
                )
26✔
299
        } else {
26✔
300
                stateHint = lnwallet.DeriveStateHintObfuscator(
×
301
                        chanState.RemoteChanCfg.PaymentBasePoint.PubKey,
×
302
                        chanState.LocalChanCfg.PaymentBasePoint.PubKey,
×
303
                )
×
304
        }
×
305

306
        // Get the witness script for the funding output.
307
        fundingPkScript, err := deriveFundingPkScript(chanState)
26✔
308
        if err != nil {
26✔
309
                return nil, err
×
310
        }
×
311

312
        // Get the channel opening block height.
313
        heightHint := chanState.DeriveHeightHint()
26✔
314

26✔
315
        // We'll register for a notification to be dispatched if the funding
26✔
316
        // output is spent.
26✔
317
        spendNtfn, err := cfg.notifier.RegisterSpendNtfn(
26✔
318
                &chanState.FundingOutpoint, fundingPkScript, heightHint,
26✔
319
        )
26✔
320
        if err != nil {
26✔
321
                return nil, err
×
322
        }
×
323

324
        c := &chainWatcher{
26✔
325
                cfg:                 cfg,
26✔
326
                stateHintObfuscator: stateHint,
26✔
327
                quit:                make(chan struct{}),
26✔
328
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
26✔
329
                fundingSpendNtfn:    spendNtfn,
26✔
330
        }
26✔
331

26✔
332
        // If this is a pending taproot channel, we need to register for a
26✔
333
        // confirmation notification of the funding tx. Check the docs in
26✔
334
        // `fundingConfirmedNtfn` for details.
26✔
335
        if c.cfg.chanState.IsPending && c.cfg.chanState.ChanType.IsTaproot() {
26✔
336
                confNtfn, err := cfg.notifier.RegisterConfirmationsNtfn(
×
337
                        &chanState.FundingOutpoint.Hash, fundingPkScript, 1,
×
338
                        heightHint,
×
339
                )
×
340
                if err != nil {
×
341
                        return nil, err
×
342
                }
×
343

344
                c.fundingConfirmedNtfn = confNtfn
×
345
        }
346

347
        // Mount the block consumer.
348
        c.BeatConsumer = chainio.NewBeatConsumer(c.quit, c.Name())
26✔
349

26✔
350
        return c, nil
26✔
351
}
352

353
// Compile-time check for the chainio.Consumer interface.
354
var _ chainio.Consumer = (*chainWatcher)(nil)
355

356
// Name returns the name of the watcher.
357
//
358
// NOTE: part of the `chainio.Consumer` interface.
359
func (c *chainWatcher) Name() string {
26✔
360
        return fmt.Sprintf("ChainWatcher(%v)", c.cfg.chanState.FundingOutpoint)
26✔
361
}
26✔
362

363
// Start starts all goroutines that the chainWatcher needs to perform its
364
// duties.
365
func (c *chainWatcher) Start() error {
26✔
366
        if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
26✔
367
                return nil
×
368
        }
×
369

370
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
26✔
371
                c.cfg.chanState.FundingOutpoint)
26✔
372

26✔
373
        c.wg.Add(1)
26✔
374
        go c.closeObserver()
26✔
375

26✔
376
        return nil
26✔
377
}
378

379
// Stop signals the close observer to gracefully exit.
380
func (c *chainWatcher) Stop() error {
26✔
381
        if !atomic.CompareAndSwapInt32(&c.stopped, 0, 1) {
26✔
382
                return nil
×
383
        }
×
384

385
        close(c.quit)
26✔
386

26✔
387
        c.wg.Wait()
26✔
388

26✔
389
        return nil
26✔
390
}
391

392
// SubscribeChannelEvents returns an active subscription to the set of channel
393
// events for the channel watched by this chain watcher. Once clients no longer
394
// require the subscription, they should call the Cancel() method to allow the
395
// watcher to regain those committed resources.
396
func (c *chainWatcher) SubscribeChannelEvents() *ChainEventSubscription {
26✔
397

26✔
398
        c.Lock()
26✔
399
        clientID := c.clientID
26✔
400
        c.clientID++
26✔
401
        c.Unlock()
26✔
402

26✔
403
        log.Debugf("New ChainEventSubscription(id=%v) for ChannelPoint(%v)",
26✔
404
                clientID, c.cfg.chanState.FundingOutpoint)
26✔
405

26✔
406
        sub := &ChainEventSubscription{
26✔
407
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
26✔
408
                RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
26✔
409
                LocalUnilateralClosure:  make(chan *LocalUnilateralCloseInfo, 1),
26✔
410
                CooperativeClosure:      make(chan *CooperativeCloseInfo, 1),
26✔
411
                ContractBreach:          make(chan *BreachCloseInfo, 1),
26✔
412
                Cancel: func() {
37✔
413
                        c.Lock()
11✔
414
                        delete(c.clientSubscriptions, clientID)
11✔
415
                        c.Unlock()
11✔
416
                },
11✔
417
        }
418

419
        c.Lock()
26✔
420
        c.clientSubscriptions[clientID] = sub
26✔
421
        c.Unlock()
26✔
422

26✔
423
        return sub
26✔
424
}
425

426
// handleUnknownLocalState checks whether the passed spend _could_ be a local
427
// state that for some reason is unknown to us. This could be a state published
428
// by us before we lost state, which we will try to sweep. Or it could be one
429
// of our revoked states that somehow made it to the chain. If that's the case
430
// we cannot really hope that we'll be able to get our money back, but we'll
431
// try to sweep it anyway. If this is not an unknown local state, false is
432
// returned.
433
func (c *chainWatcher) handleUnknownLocalState(
434
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
435
        chainSet *chainSet) (bool, error) {
11✔
436

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

11✔
442
        // First, we'll re-derive our commitment point for this state since
11✔
443
        // this is what we use to randomize each of the keys for this state.
11✔
444
        commitSecret, err := c.cfg.chanState.RevocationProducer.AtIndex(
11✔
445
                broadcastStateNum,
11✔
446
        )
11✔
447
        if err != nil {
11✔
448
                return false, err
×
449
        }
×
450
        commitPoint := input.ComputeCommitmentPoint(commitSecret[:])
11✔
451

11✔
452
        // Now that we have the commit point, we'll derive the tweaked local
11✔
453
        // and remote keys for this state. We use our point as only we can
11✔
454
        // revoke our own commitment.
11✔
455
        commitKeyRing := lnwallet.DeriveCommitmentKeys(
11✔
456
                commitPoint, lntypes.Local, c.cfg.chanState.ChanType,
11✔
457
                &c.cfg.chanState.LocalChanCfg, &c.cfg.chanState.RemoteChanCfg,
11✔
458
        )
11✔
459

11✔
460
        auxResult, err := fn.MapOptionZ(
11✔
461
                c.cfg.auxLeafStore,
11✔
462
                //nolint:ll
11✔
463
                func(s lnwallet.AuxLeafStore) fn.Result[lnwallet.CommitDiffAuxResult] {
11✔
464
                        return s.FetchLeavesFromCommit(
×
465
                                lnwallet.NewAuxChanState(c.cfg.chanState),
×
466
                                c.cfg.chanState.LocalCommitment, *commitKeyRing,
×
467
                                lntypes.Local,
×
468
                        )
×
469
                },
×
470
        ).Unpack()
471
        if err != nil {
11✔
472
                return false, fmt.Errorf("unable to fetch aux leaves: %w", err)
×
473
        }
×
474

475
        // With the keys derived, we'll construct the remote script that'll be
476
        // present if they have a non-dust balance on the commitment.
477
        var leaseExpiry uint32
11✔
478
        if c.cfg.chanState.ChanType.HasLeaseExpiration() {
11✔
479
                leaseExpiry = c.cfg.chanState.ThawHeight
×
480
        }
×
481

482
        remoteAuxLeaf := fn.FlatMapOption(
11✔
483
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
11✔
484
                        return l.RemoteAuxLeaf
×
485
                },
×
486
        )(auxResult.AuxLeaves)
487
        remoteScript, _, err := lnwallet.CommitScriptToRemote(
11✔
488
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
11✔
489
                commitKeyRing.ToRemoteKey, leaseExpiry,
11✔
490
                remoteAuxLeaf,
11✔
491
        )
11✔
492
        if err != nil {
11✔
493
                return false, err
×
494
        }
×
495

496
        // Next, we'll derive our script that includes the revocation base for
497
        // the remote party allowing them to claim this output before the CSV
498
        // delay if we breach.
499
        localAuxLeaf := fn.FlatMapOption(
11✔
500
                func(l lnwallet.CommitAuxLeaves) input.AuxTapLeaf {
11✔
501
                        return l.LocalAuxLeaf
×
502
                },
×
503
        )(auxResult.AuxLeaves)
504
        localScript, err := lnwallet.CommitScriptToSelf(
11✔
505
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
11✔
506
                commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
11✔
507
                uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
11✔
508
                localAuxLeaf,
11✔
509
        )
11✔
510
        if err != nil {
11✔
511
                return false, err
×
512
        }
×
513

514
        // With all our scripts assembled, we'll examine the outputs of the
515
        // commitment transaction to determine if this is a local force close
516
        // or not.
517
        ourCommit := false
11✔
518
        for _, output := range commitSpend.SpendingTx.TxOut {
27✔
519
                pkScript := output.PkScript
16✔
520

16✔
521
                switch {
16✔
522
                case bytes.Equal(localScript.PkScript(), pkScript):
4✔
523
                        ourCommit = true
4✔
524

525
                case bytes.Equal(remoteScript.PkScript(), pkScript):
4✔
526
                        ourCommit = true
4✔
527
                }
528
        }
529

530
        // If the script is not present, this cannot be our commit.
531
        if !ourCommit {
15✔
532
                return false, nil
4✔
533
        }
4✔
534

535
        log.Warnf("Detected local unilateral close of unknown state %v "+
7✔
536
                "(our state=%v)", broadcastStateNum,
7✔
537
                chainSet.localCommit.CommitHeight)
7✔
538

7✔
539
        // If this is our commitment transaction, then we try to act even
7✔
540
        // though we won't be able to sweep HTLCs.
7✔
541
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
7✔
542
        if err := c.dispatchLocalForceClose(
7✔
543
                commitSpend, broadcastStateNum, chainSet.commitSet,
7✔
544
        ); err != nil {
7✔
545
                return false, fmt.Errorf("unable to handle local"+
×
546
                        "close for chan_point=%v: %v",
×
547
                        c.cfg.chanState.FundingOutpoint, err)
×
548
        }
×
549

550
        return true, nil
7✔
551
}
552

553
// chainSet includes all the information we need to dispatch a channel close
554
// event to any subscribers.
555
type chainSet struct {
556
        // remoteStateNum is the commitment number of the lowest valid
557
        // commitment the remote party holds from our PoV. This value is used
558
        // to determine if the remote party is playing a state that's behind,
559
        // in line, or ahead of the latest state we know for it.
560
        remoteStateNum uint64
561

562
        // commitSet includes information pertaining to the set of active HTLCs
563
        // on each commitment.
564
        commitSet CommitSet
565

566
        // remoteCommit is the current commitment of the remote party.
567
        remoteCommit channeldb.ChannelCommitment
568

569
        // localCommit is our current commitment.
570
        localCommit channeldb.ChannelCommitment
571

572
        // remotePendingCommit points to the dangling commitment of the remote
573
        // party, if it exists. If there's no dangling commitment, then this
574
        // pointer will be nil.
575
        remotePendingCommit *channeldb.ChannelCommitment
576
}
577

578
// newChainSet creates a new chainSet given the current up to date channel
579
// state.
580
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
15✔
581
        // First, we'll grab the current unrevoked commitments for ourselves
15✔
582
        // and the remote party.
15✔
583
        localCommit, remoteCommit, err := chanState.LatestCommitments()
15✔
584
        if err != nil {
15✔
585
                return nil, fmt.Errorf("unable to fetch channel state for "+
×
586
                        "chan_point=%v: %v", chanState.FundingOutpoint, err)
×
587
        }
×
588

589
        log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
15✔
590
                chanState.FundingOutpoint, chanState.ChanType,
15✔
591
                spew.Sdump(localCommit))
15✔
592
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
15✔
593
                chanState.FundingOutpoint, chanState.ChanType,
15✔
594
                spew.Sdump(remoteCommit))
15✔
595

15✔
596
        // Fetch the current known commit height for the remote party, and
15✔
597
        // their pending commitment chain tip if it exists.
15✔
598
        remoteStateNum := remoteCommit.CommitHeight
15✔
599
        remoteChainTip, err := chanState.RemoteCommitChainTip()
15✔
600
        if err != nil && err != channeldb.ErrNoPendingCommit {
15✔
601
                return nil, fmt.Errorf("unable to obtain chain tip for "+
×
602
                        "ChannelPoint(%v): %v",
×
603
                        chanState.FundingOutpoint, err)
×
604
        }
×
605

606
        // Now that we have all the possible valid commitments, we'll make the
607
        // CommitSet the ChannelArbitrator will need in order to carry out its
608
        // duty.
609
        commitSet := CommitSet{
15✔
610
                HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
15✔
611
                        LocalHtlcSet:  localCommit.Htlcs,
15✔
612
                        RemoteHtlcSet: remoteCommit.Htlcs,
15✔
613
                },
15✔
614
        }
15✔
615

15✔
616
        var remotePendingCommit *channeldb.ChannelCommitment
15✔
617
        if remoteChainTip != nil {
16✔
618
                remotePendingCommit = &remoteChainTip.Commitment
1✔
619
                log.Tracef("ChannelPoint(%v): remote_pending_commit_type=%v, "+
1✔
620
                        "remote_pending_commit=%v", chanState.FundingOutpoint,
1✔
621
                        chanState.ChanType,
1✔
622
                        spew.Sdump(remoteChainTip.Commitment))
1✔
623

1✔
624
                htlcs := remoteChainTip.Commitment.Htlcs
1✔
625
                commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
1✔
626
        }
1✔
627

628
        // We'll now retrieve the latest state of the revocation store so we
629
        // can populate the revocation information within the channel state
630
        // object that we have.
631
        //
632
        // TODO(roasbeef): mutation is bad mkay
633
        _, err = chanState.RemoteRevocationStore()
15✔
634
        if err != nil {
15✔
635
                return nil, fmt.Errorf("unable to fetch revocation state for "+
×
636
                        "chan_point=%v", chanState.FundingOutpoint)
×
637
        }
×
638

639
        return &chainSet{
15✔
640
                remoteStateNum:      remoteStateNum,
15✔
641
                commitSet:           commitSet,
15✔
642
                localCommit:         *localCommit,
15✔
643
                remoteCommit:        *remoteCommit,
15✔
644
                remotePendingCommit: remotePendingCommit,
15✔
645
        }, nil
15✔
646
}
647

648
// closeObserver is a dedicated goroutine that will watch for any closes of the
649
// channel that it's watching on chain. In the event of an on-chain event, the
650
// close observer will assembled the proper materials required to claim the
651
// funds of the channel on-chain (if required), then dispatch these as
652
// notifications to all subscribers.
653
func (c *chainWatcher) closeObserver() {
26✔
654
        defer c.wg.Done()
26✔
655
        defer c.fundingSpendNtfn.Cancel()
26✔
656

26✔
657
        log.Infof("Close observer for ChannelPoint(%v) active",
26✔
658
                c.cfg.chanState.FundingOutpoint)
26✔
659

26✔
660
        for {
81✔
661
                select {
55✔
662
                // A new block is received, we will check whether this block
663
                // contains a spending tx that we are interested in.
664
                case beat := <-c.BlockbeatChan:
15✔
665
                        log.Debugf("ChainWatcher(%v) received blockbeat %v",
15✔
666
                                c.cfg.chanState.FundingOutpoint, beat.Height())
15✔
667

15✔
668
                        // Process the block.
15✔
669
                        c.handleBlockbeat(beat)
15✔
670

671
                // If the funding outpoint is spent, we now go ahead and handle
672
                // it. Note that we cannot rely solely on the `block` event
673
                // above to trigger a close event, as deep down, the receiving
674
                // of block notifications and the receiving of spending
675
                // notifications are done in two different goroutines, so the
676
                // expected order: [receive block -> receive spend] is not
677
                // guaranteed .
678
                case spend, ok := <-c.fundingSpendNtfn.Spend:
14✔
679
                        // If the channel was closed, then this means that the
14✔
680
                        // notifier exited, so we will as well.
14✔
681
                        if !ok {
14✔
682
                                return
×
683
                        }
×
684

685
                        err := c.handleCommitSpend(spend)
14✔
686
                        if err != nil {
14✔
687
                                log.Errorf("Failed to handle commit spend: %v",
×
688
                                        err)
×
689
                        }
×
690

691
                // The chainWatcher has been signalled to exit, so we'll do so
692
                // now.
693
                case <-c.quit:
26✔
694
                        return
26✔
695
                }
696
        }
697
}
698

699
// handleKnownLocalState checks whether the passed spend is a local state that
700
// is known to us (the current state). If so we will act on this state using
701
// the passed chainSet. If this is not a known local state, false is returned.
702
func (c *chainWatcher) handleKnownLocalState(
703
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
704
        chainSet *chainSet) (bool, error) {
15✔
705

15✔
706
        // If the channel is recovered, we won't have a local commit to check
15✔
707
        // against, so immediately return.
15✔
708
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
15✔
709
                return false, nil
×
710
        }
×
711

712
        commitTxBroadcast := commitSpend.SpendingTx
15✔
713
        commitHash := commitTxBroadcast.TxHash()
15✔
714

15✔
715
        // Check whether our latest local state hit the chain.
15✔
716
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
28✔
717
                return false, nil
13✔
718
        }
13✔
719

720
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
2✔
721
        if err := c.dispatchLocalForceClose(
2✔
722
                commitSpend, broadcastStateNum, chainSet.commitSet,
2✔
723
        ); err != nil {
2✔
724
                return false, fmt.Errorf("unable to handle local"+
×
725
                        "close for chan_point=%v: %v",
×
726
                        c.cfg.chanState.FundingOutpoint, err)
×
727
        }
×
728

729
        return true, nil
2✔
730
}
731

732
// handleKnownRemoteState checks whether the passed spend is a remote state
733
// that is known to us (a revoked, current or pending state). If so we will act
734
// on this state using the passed chainSet. If this is not a known remote
735
// state, false is returned.
736
func (c *chainWatcher) handleKnownRemoteState(
737
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
738
        chainSet *chainSet) (bool, error) {
13✔
739

13✔
740
        // If the channel is recovered, we won't have any remote commit to
13✔
741
        // check against, so imemdiately return.
13✔
742
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
13✔
743
                return false, nil
×
744
        }
×
745

746
        commitTxBroadcast := commitSpend.SpendingTx
13✔
747
        commitHash := commitTxBroadcast.TxHash()
13✔
748

13✔
749
        switch {
13✔
750
        // If the spending transaction matches the current latest state, then
751
        // they've initiated a unilateral close. So we'll trigger the
752
        // unilateral close signal so subscribers can clean up the state as
753
        // necessary.
754
        case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
1✔
755
                log.Infof("Remote party broadcast base set, "+
1✔
756
                        "commit_num=%v", chainSet.remoteStateNum)
1✔
757

1✔
758
                chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
1✔
759
                err := c.dispatchRemoteForceClose(
1✔
760
                        commitSpend, chainSet.remoteCommit,
1✔
761
                        chainSet.commitSet,
1✔
762
                        c.cfg.chanState.RemoteCurrentRevocation,
1✔
763
                )
1✔
764
                if err != nil {
1✔
765
                        return false, fmt.Errorf("unable to handle remote "+
×
766
                                "close for chan_point=%v: %v",
×
767
                                c.cfg.chanState.FundingOutpoint, err)
×
768
                }
×
769

770
                return true, nil
1✔
771

772
        // We'll also handle the case of the remote party broadcasting
773
        // their commitment transaction which is one height above ours.
774
        // This case can arise when we initiate a state transition, but
775
        // the remote party has a fail crash _after_ accepting the new
776
        // state, but _before_ sending their signature to us.
777
        case chainSet.remotePendingCommit != nil &&
778
                chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
1✔
779

1✔
780
                log.Infof("Remote party broadcast pending set, "+
1✔
781
                        "commit_num=%v", chainSet.remoteStateNum+1)
1✔
782

1✔
783
                chainSet.commitSet.ConfCommitKey = fn.Some(RemotePendingHtlcSet)
1✔
784
                err := c.dispatchRemoteForceClose(
1✔
785
                        commitSpend, *chainSet.remotePendingCommit,
1✔
786
                        chainSet.commitSet,
1✔
787
                        c.cfg.chanState.RemoteNextRevocation,
1✔
788
                )
1✔
789
                if err != nil {
1✔
790
                        return false, fmt.Errorf("unable to handle remote "+
×
791
                                "close for chan_point=%v: %v",
×
792
                                c.cfg.chanState.FundingOutpoint, err)
×
793
                }
×
794

795
                return true, nil
1✔
796
        }
797

798
        // This is neither a remote force close or a "future" commitment, we
799
        // now check whether it's a remote breach and properly handle it.
800
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
11✔
801
}
802

803
// handlePossibleBreach checks whether the remote has breached and dispatches a
804
// breach resolution to claim funds.
805
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
806
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
11✔
807

11✔
808
        // We check if we have a revoked state at this state num that matches
11✔
809
        // the spend transaction.
11✔
810
        spendHeight := uint32(commitSpend.SpendingHeight)
11✔
811
        retribution, err := lnwallet.NewBreachRetribution(
11✔
812
                c.cfg.chanState, broadcastStateNum, spendHeight,
11✔
813
                commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
11✔
814
        )
11✔
815

11✔
816
        switch {
11✔
817
        // If we had no log entry at this height, this was not a revoked state.
818
        case err == channeldb.ErrLogEntryNotFound:
8✔
819
                return false, nil
8✔
820
        case err == channeldb.ErrNoPastDeltas:
3✔
821
                return false, nil
3✔
822

823
        case err != nil:
×
824
                return false, fmt.Errorf("unable to create breach "+
×
825
                        "retribution: %v", err)
×
826
        }
827

828
        // We found a revoked state at this height, but it could still be our
829
        // own broadcasted state we are looking at. Therefore check that the
830
        // commit matches before assuming it was a breach.
831
        commitHash := commitSpend.SpendingTx.TxHash()
×
832
        if retribution.BreachTxHash != commitHash {
×
833
                return false, nil
×
834
        }
×
835

836
        // Create an AnchorResolution for the breached state.
837
        anchorRes, err := lnwallet.NewAnchorResolution(
×
838
                c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
×
839
                lntypes.Remote,
×
840
        )
×
841
        if err != nil {
×
842
                return false, fmt.Errorf("unable to create anchor "+
×
843
                        "resolution: %v", err)
×
844
        }
×
845

846
        // We'll set the ConfCommitKey here as the remote htlc set. This is
847
        // only used to ensure a nil-pointer-dereference doesn't occur and is
848
        // not used otherwise. The HTLC's may not exist for the
849
        // RemotePendingHtlcSet.
850
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
×
851

×
852
        // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
×
853
        // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
×
854
        // broadcast to allow subscribers to swiftly dispatch justice!!!
×
855
        err = c.dispatchContractBreach(
×
856
                commitSpend, chainSet, broadcastStateNum, retribution,
×
857
                anchorRes,
×
858
        )
×
859
        if err != nil {
×
860
                return false, fmt.Errorf("unable to handle channel "+
×
861
                        "breach for chan_point=%v: %v",
×
862
                        c.cfg.chanState.FundingOutpoint, err)
×
863
        }
×
864

865
        return true, nil
×
866
}
867

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

4✔
877
        log.Warnf("Remote node broadcast state #%v, "+
4✔
878
                "which is more than 1 beyond best known "+
4✔
879
                "state #%v!!! Attempting recovery...",
4✔
880
                broadcastStateNum, chainSet.remoteStateNum)
4✔
881

4✔
882
        // If this isn't a tweakless commitment, then we'll need to wait for
4✔
883
        // the remote party's latest unrevoked commitment point to be presented
4✔
884
        // to us as we need this to sweep. Otherwise, we can dispatch the
4✔
885
        // remote close and sweep immediately using a fake commitPoint as it
4✔
886
        // isn't actually needed for recovery anymore.
4✔
887
        commitPoint := c.cfg.chanState.RemoteCurrentRevocation
4✔
888
        tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
4✔
889
        if !tweaklessCommit {
8✔
890
                commitPoint = c.waitForCommitmentPoint()
4✔
891
                if commitPoint == nil {
4✔
892
                        return false, fmt.Errorf("unable to get commit point")
×
893
                }
×
894

895
                log.Infof("Recovered commit point(%x) for "+
4✔
896
                        "channel(%v)! Now attempting to use it to "+
4✔
897
                        "sweep our funds...",
4✔
898
                        commitPoint.SerializeCompressed(),
4✔
899
                        c.cfg.chanState.FundingOutpoint)
4✔
900
        } else {
×
901
                log.Infof("ChannelPoint(%v) is tweakless, "+
×
902
                        "moving to sweep directly on chain",
×
903
                        c.cfg.chanState.FundingOutpoint)
×
904
        }
×
905

906
        // Since we don't have the commitment stored for this state, we'll just
907
        // pass an empty commitment within the commitment set. Note that this
908
        // means we won't be able to recover any HTLC funds.
909
        //
910
        // TODO(halseth): can we try to recover some HTLCs?
911
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
4✔
912
        err := c.dispatchRemoteForceClose(
4✔
913
                commitSpend, channeldb.ChannelCommitment{},
4✔
914
                chainSet.commitSet, commitPoint,
4✔
915
        )
4✔
916
        if err != nil {
4✔
917
                return false, fmt.Errorf("unable to handle remote "+
×
918
                        "close for chan_point=%v: %v",
×
919
                        c.cfg.chanState.FundingOutpoint, err)
×
920
        }
×
921

922
        return true, nil
4✔
923
}
924

925
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
926
// to a script that the wallet controls or the channel defines as its delivery
927
// script . If no outputs pay to us (determined by these criteria), then we
928
// return zero. This is possible as our output may have been trimmed due to
929
// being dust.
930
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
×
931
        // There are two main cases we have to handle here. First, in the coop
×
932
        // close case we will always have saved the delivery address we used
×
933
        // whether it was from the upfront shutdown, from the delivery address
×
934
        // requested at close time, or even an automatically generated one. All
×
935
        // coop-close cases can be identified in the following manner:
×
936
        shutdown, _ := c.cfg.chanState.ShutdownInfo()
×
937
        oDeliveryAddr := fn.MapOption(
×
938
                func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
×
939
                        return i.DeliveryScript.Val
×
940
                })(shutdown)
×
941

942
        // Here we define a function capable of identifying whether an output
943
        // corresponds with our local delivery script from a ShutdownInfo if we
944
        // have a ShutdownInfo for this chainWatcher's underlying channel.
945
        //
946
        // isDeliveryOutput :: *TxOut -> bool
947
        isDeliveryOutput := func(o *wire.TxOut) bool {
×
948
                return fn.ElimOption(
×
949
                        oDeliveryAddr,
×
950
                        // If we don't have a delivery addr, then the output
×
951
                        // can't match it.
×
952
                        func() bool { return false },
×
953
                        // Otherwise if the PkScript of the TxOut matches our
954
                        // delivery script then this is a delivery output.
955
                        func(a lnwire.DeliveryAddress) bool {
×
956
                                return slices.Equal(a, o.PkScript)
×
957
                        },
×
958
                )
959
        }
960

961
        // Here we define a function capable of identifying whether an output
962
        // belongs to the LND wallet. We use this as a heuristic in the case
963
        // where we might be looking for spendable force closure outputs.
964
        //
965
        // isWalletOutput :: *TxOut -> bool
966
        isWalletOutput := func(out *wire.TxOut) bool {
×
967
                _, addrs, _, err := txscript.ExtractPkScriptAddrs(
×
968
                        // Doesn't matter what net we actually pass in.
×
969
                        out.PkScript, &chaincfg.TestNet3Params,
×
970
                )
×
971
                if err != nil {
×
972
                        return false
×
973
                }
×
974

975
                return fn.Any(addrs, c.cfg.isOurAddr)
×
976
        }
977

978
        // Grab all of the outputs that correspond with our delivery address
979
        // or our wallet is aware of.
980
        outs := fn.Filter(tx.TxOut, fn.PredOr(isDeliveryOutput, isWalletOutput))
×
981

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

985
        // Return the sum.
986
        return btcutil.Amount(fn.Sum(vals))
×
987
}
988

989
// dispatchCooperativeClose processed a detect cooperative channel closure.
990
// We'll use the spending transaction to locate our output within the
991
// transaction, then clean up the database state. We'll also dispatch a
992
// notification to all subscribers that the channel has been closed in this
993
// manner.
994
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
×
995
        broadcastTx := commitSpend.SpendingTx
×
996

×
997
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
×
998
                c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
×
999

×
1000
        // If the input *is* final, then we'll check to see which output is
×
1001
        // ours.
×
1002
        localAmt := c.toSelfAmount(broadcastTx)
×
1003

×
1004
        // Once this is known, we'll mark the state as fully closed in the
×
1005
        // database. We can do this as a cooperatively closed channel has all
×
1006
        // its outputs resolved after only one confirmation.
×
1007
        closeSummary := &channeldb.ChannelCloseSummary{
×
1008
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
×
1009
                ChainHash:               c.cfg.chanState.ChainHash,
×
1010
                ClosingTXID:             *commitSpend.SpenderTxHash,
×
1011
                RemotePub:               c.cfg.chanState.IdentityPub,
×
1012
                Capacity:                c.cfg.chanState.Capacity,
×
1013
                CloseHeight:             uint32(commitSpend.SpendingHeight),
×
1014
                SettledBalance:          localAmt,
×
1015
                CloseType:               channeldb.CooperativeClose,
×
1016
                ShortChanID:             c.cfg.chanState.ShortChanID(),
×
1017
                IsPending:               true,
×
1018
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
×
1019
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
×
1020
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
1021
        }
×
1022

×
1023
        // Attempt to add a channel sync message to the close summary.
×
1024
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
×
1025
        if err != nil {
×
1026
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1027
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1028
        } else {
×
1029
                closeSummary.LastChanSyncMsg = chanSync
×
1030
        }
×
1031

1032
        // Create a summary of all the information needed to handle the
1033
        // cooperative closure.
1034
        closeInfo := &CooperativeCloseInfo{
×
1035
                ChannelCloseSummary: closeSummary,
×
1036
        }
×
1037

×
1038
        // With the event processed, we'll now notify all subscribers of the
×
1039
        // event.
×
1040
        c.Lock()
×
1041
        for _, sub := range c.clientSubscriptions {
×
1042
                select {
×
1043
                case sub.CooperativeClosure <- closeInfo:
×
1044
                case <-c.quit:
×
1045
                        c.Unlock()
×
1046
                        return fmt.Errorf("exiting")
×
1047
                }
1048
        }
1049
        c.Unlock()
×
1050

×
1051
        return nil
×
1052
}
1053

1054
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
1055
func (c *chainWatcher) dispatchLocalForceClose(
1056
        commitSpend *chainntnfs.SpendDetail,
1057
        stateNum uint64, commitSet CommitSet) error {
9✔
1058

9✔
1059
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
9✔
1060
                "detected", c.cfg.chanState.FundingOutpoint)
9✔
1061

9✔
1062
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
9✔
1063
                c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
9✔
1064
                c.cfg.auxLeafStore, c.cfg.auxResolver,
9✔
1065
        )
9✔
1066
        if err != nil {
9✔
1067
                return err
×
1068
        }
×
1069

1070
        // As we've detected that the channel has been closed, immediately
1071
        // creating a close summary for future usage by related sub-systems.
1072
        chanSnapshot := forceClose.ChanSnapshot
9✔
1073
        closeSummary := &channeldb.ChannelCloseSummary{
9✔
1074
                ChanPoint:               chanSnapshot.ChannelPoint,
9✔
1075
                ChainHash:               chanSnapshot.ChainHash,
9✔
1076
                ClosingTXID:             forceClose.CloseTx.TxHash(),
9✔
1077
                RemotePub:               &chanSnapshot.RemoteIdentity,
9✔
1078
                Capacity:                chanSnapshot.Capacity,
9✔
1079
                CloseType:               channeldb.LocalForceClose,
9✔
1080
                IsPending:               true,
9✔
1081
                ShortChanID:             c.cfg.chanState.ShortChanID(),
9✔
1082
                CloseHeight:             uint32(commitSpend.SpendingHeight),
9✔
1083
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
9✔
1084
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
9✔
1085
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
9✔
1086
        }
9✔
1087

9✔
1088
        resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
9✔
1089
                fmt.Errorf("resolutions not found"),
9✔
1090
        )
9✔
1091
        if err != nil {
9✔
1092
                return err
×
1093
        }
×
1094

1095
        // If our commitment output isn't dust or we have active HTLC's on the
1096
        // commitment transaction, then we'll populate the balances on the
1097
        // close channel summary.
1098
        if resolutions.CommitResolution != nil {
15✔
1099
                localBalance := chanSnapshot.LocalBalance.ToSatoshis()
6✔
1100
                closeSummary.SettledBalance = localBalance
6✔
1101
                closeSummary.TimeLockedBalance = localBalance
6✔
1102
        }
6✔
1103

1104
        if resolutions.HtlcResolutions != nil {
18✔
1105
                for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
9✔
1106
                        htlcValue := btcutil.Amount(
×
1107
                                htlc.SweepSignDesc.Output.Value,
×
1108
                        )
×
1109
                        closeSummary.TimeLockedBalance += htlcValue
×
1110
                }
×
1111
        }
1112

1113
        // Attempt to add a channel sync message to the close summary.
1114
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
9✔
1115
        if err != nil {
9✔
1116
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1117
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1118
        } else {
9✔
1119
                closeSummary.LastChanSyncMsg = chanSync
9✔
1120
        }
9✔
1121

1122
        // With the event processed, we'll now notify all subscribers of the
1123
        // event.
1124
        closeInfo := &LocalUnilateralCloseInfo{
9✔
1125
                SpendDetail:            commitSpend,
9✔
1126
                LocalForceCloseSummary: forceClose,
9✔
1127
                ChannelCloseSummary:    closeSummary,
9✔
1128
                CommitSet:              commitSet,
9✔
1129
        }
9✔
1130
        c.Lock()
9✔
1131
        for _, sub := range c.clientSubscriptions {
18✔
1132
                select {
9✔
1133
                case sub.LocalUnilateralClosure <- closeInfo:
9✔
1134
                case <-c.quit:
×
1135
                        c.Unlock()
×
1136
                        return fmt.Errorf("exiting")
×
1137
                }
1138
        }
1139
        c.Unlock()
9✔
1140

9✔
1141
        return nil
9✔
1142
}
1143

1144
// dispatchRemoteForceClose processes a detected unilateral channel closure by
1145
// the remote party. This function will prepare a UnilateralCloseSummary which
1146
// will then be sent to any subscribers allowing them to resolve all our funds
1147
// in the channel on chain. Once this close summary is prepared, all registered
1148
// subscribers will receive a notification of this event. The commitPoint
1149
// argument should be set to the per_commitment_point corresponding to the
1150
// spending commitment.
1151
//
1152
// NOTE: The remoteCommit argument should be set to the stored commitment for
1153
// this particular state. If we don't have the commitment stored (should only
1154
// happen in case we have lost state) it should be set to an empty struct, in
1155
// which case we will attempt to sweep the non-HTLC output using the passed
1156
// commitPoint.
1157
func (c *chainWatcher) dispatchRemoteForceClose(
1158
        commitSpend *chainntnfs.SpendDetail,
1159
        remoteCommit channeldb.ChannelCommitment,
1160
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
6✔
1161

6✔
1162
        log.Infof("Unilateral close of ChannelPoint(%v) "+
6✔
1163
                "detected", c.cfg.chanState.FundingOutpoint)
6✔
1164

6✔
1165
        // First, we'll create a closure summary that contains all the
6✔
1166
        // materials required to let each subscriber sweep the funds in the
6✔
1167
        // channel on-chain.
6✔
1168
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
6✔
1169
                c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
6✔
1170
                commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
6✔
1171
        )
6✔
1172
        if err != nil {
6✔
1173
                return err
×
1174
        }
×
1175

1176
        // With the event processed, we'll now notify all subscribers of the
1177
        // event.
1178
        c.Lock()
6✔
1179
        for _, sub := range c.clientSubscriptions {
12✔
1180
                select {
6✔
1181
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
1182
                        UnilateralCloseSummary: uniClose,
1183
                        CommitSet:              commitSet,
1184
                }:
6✔
1185
                case <-c.quit:
×
1186
                        c.Unlock()
×
1187
                        return fmt.Errorf("exiting")
×
1188
                }
1189
        }
1190
        c.Unlock()
6✔
1191

6✔
1192
        return nil
6✔
1193
}
1194

1195
// dispatchContractBreach processes a detected contract breached by the remote
1196
// party. This method is to be called once we detect that the remote party has
1197
// broadcast a prior revoked commitment state. This method well prepare all the
1198
// materials required to bring the cheater to justice, then notify all
1199
// registered subscribers of this event.
1200
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
1201
        chainSet *chainSet, broadcastStateNum uint64,
1202
        retribution *lnwallet.BreachRetribution,
1203
        anchorRes *lnwallet.AnchorResolution) error {
×
1204

×
1205
        log.Warnf("Remote peer has breached the channel contract for "+
×
1206
                "ChannelPoint(%v). Revoked state #%v was broadcast!!!",
×
1207
                c.cfg.chanState.FundingOutpoint, broadcastStateNum)
×
1208

×
1209
        if err := c.cfg.chanState.MarkBorked(); err != nil {
×
1210
                return fmt.Errorf("unable to mark channel as borked: %w", err)
×
1211
        }
×
1212

1213
        spendHeight := uint32(spendEvent.SpendingHeight)
×
1214

×
1215
        log.Debugf("Punishment breach retribution created: %v",
×
1216
                lnutils.NewLogClosure(func() string {
×
1217
                        retribution.KeyRing.LocalHtlcKey = nil
×
1218
                        retribution.KeyRing.RemoteHtlcKey = nil
×
1219
                        retribution.KeyRing.ToLocalKey = nil
×
1220
                        retribution.KeyRing.ToRemoteKey = nil
×
1221
                        retribution.KeyRing.RevocationKey = nil
×
1222
                        return spew.Sdump(retribution)
×
1223
                }))
×
1224

1225
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
×
1226
        closeSummary := channeldb.ChannelCloseSummary{
×
1227
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
×
1228
                ChainHash:               c.cfg.chanState.ChainHash,
×
1229
                ClosingTXID:             *spendEvent.SpenderTxHash,
×
1230
                CloseHeight:             spendHeight,
×
1231
                RemotePub:               c.cfg.chanState.IdentityPub,
×
1232
                Capacity:                c.cfg.chanState.Capacity,
×
1233
                SettledBalance:          settledBalance,
×
1234
                CloseType:               channeldb.BreachClose,
×
1235
                IsPending:               true,
×
1236
                ShortChanID:             c.cfg.chanState.ShortChanID(),
×
1237
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
×
1238
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
×
1239
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
1240
        }
×
1241

×
1242
        // Attempt to add a channel sync message to the close summary.
×
1243
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
×
1244
        if err != nil {
×
1245
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1246
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1247
        } else {
×
1248
                closeSummary.LastChanSyncMsg = chanSync
×
1249
        }
×
1250

1251
        // Hand the retribution info over to the BreachArbitrator. This function
1252
        // will wait for a response from the breach arbiter and then proceed to
1253
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1254
        // will then mark the channel as closed after resolutions and the
1255
        // commit set are logged in the arbitrator log.
1256
        if err := c.cfg.contractBreach(retribution); err != nil {
×
1257
                log.Errorf("unable to hand breached contract off to "+
×
1258
                        "BreachArbitrator: %v", err)
×
1259
                return err
×
1260
        }
×
1261

1262
        breachRes := &BreachResolution{
×
1263
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
×
1264
        }
×
1265

×
1266
        breachInfo := &BreachCloseInfo{
×
1267
                CommitHash:       spendEvent.SpendingTx.TxHash(),
×
1268
                BreachResolution: breachRes,
×
1269
                AnchorResolution: anchorRes,
×
1270
                CommitSet:        chainSet.commitSet,
×
1271
                CloseSummary:     closeSummary,
×
1272
        }
×
1273

×
1274
        // With the event processed and channel closed, we'll now notify all
×
1275
        // subscribers of the event.
×
1276
        c.Lock()
×
1277
        for _, sub := range c.clientSubscriptions {
×
1278
                select {
×
1279
                case sub.ContractBreach <- breachInfo:
×
1280
                case <-c.quit:
×
1281
                        c.Unlock()
×
1282
                        return fmt.Errorf("quitting")
×
1283
                }
1284
        }
1285
        c.Unlock()
×
1286

×
1287
        return nil
×
1288
}
1289

1290
// waitForCommitmentPoint waits for the commitment point to be inserted into
1291
// the local database. We'll use this method in the DLP case, to wait for the
1292
// remote party to send us their point, as we can't proceed until we have that.
1293
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
4✔
1294
        // If we are lucky, the remote peer sent us the correct commitment
4✔
1295
        // point during channel sync, such that we can sweep our funds. If we
4✔
1296
        // cannot find the commit point, there's not much we can do other than
4✔
1297
        // wait for us to retrieve it. We will attempt to retrieve it from the
4✔
1298
        // peer each time we connect to it.
4✔
1299
        //
4✔
1300
        // TODO(halseth): actively initiate re-connection to the peer?
4✔
1301
        backoff := minCommitPointPollTimeout
4✔
1302
        for {
8✔
1303
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
4✔
1304
                if err == nil {
8✔
1305
                        return commitPoint
4✔
1306
                }
4✔
1307

1308
                log.Errorf("Unable to retrieve commitment point for "+
×
1309
                        "channel(%v) with lost state: %v. Retrying in %v.",
×
1310
                        c.cfg.chanState.FundingOutpoint, err, backoff)
×
1311

×
1312
                select {
×
1313
                // Wait before retrying, with an exponential backoff.
1314
                case <-time.After(backoff):
×
1315
                        backoff = 2 * backoff
×
1316
                        if backoff > maxCommitPointPollTimeout {
×
1317
                                backoff = maxCommitPointPollTimeout
×
1318
                        }
×
1319

1320
                case <-c.quit:
×
1321
                        return nil
×
1322
                }
1323
        }
1324
}
1325

1326
// deriveFundingPkScript derives the script used in the funding output.
1327
func deriveFundingPkScript(chanState *channeldb.OpenChannel) ([]byte, error) {
26✔
1328
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
26✔
1329
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
26✔
1330

26✔
1331
        var (
26✔
1332
                err             error
26✔
1333
                fundingPkScript []byte
26✔
1334
        )
26✔
1335

26✔
1336
        if chanState.ChanType.IsTaproot() {
26✔
1337
                fundingPkScript, _, err = input.GenTaprootFundingScript(
×
1338
                        localKey, remoteKey, 0, chanState.TapscriptRoot,
×
1339
                )
×
1340
                if err != nil {
×
1341
                        return nil, err
×
1342
                }
×
1343
        } else {
26✔
1344
                multiSigScript, err := input.GenMultiSigScript(
26✔
1345
                        localKey.SerializeCompressed(),
26✔
1346
                        remoteKey.SerializeCompressed(),
26✔
1347
                )
26✔
1348
                if err != nil {
26✔
1349
                        return nil, err
×
1350
                }
×
1351
                fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
26✔
1352
                if err != nil {
26✔
1353
                        return nil, err
×
1354
                }
×
1355
        }
1356

1357
        return fundingPkScript, nil
26✔
1358
}
1359

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

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

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

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

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

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

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

1421
                return nil
×
1422
        }
1423

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

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

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

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

×
1458
        return nil
×
1459
}
1460

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

1480
                log.Debugf("Found spend details for funding output: %v",
1✔
1481
                        spend.SpenderTxHash)
1✔
1482

1✔
1483
                return spend
1✔
1484

1485
        default:
14✔
1486
        }
1487

1488
        return nil
14✔
1489
}
1490

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

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

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

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

×
1512
                return true
×
1513

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

×
1517
                return false
×
1518
        }
1519
}
1520

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

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

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

14✔
1547
                return
14✔
1548
        }
14✔
1549

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