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

lightningnetwork / lnd / 13980275562

20 Mar 2025 10:06PM UTC coverage: 58.6% (-10.2%) from 68.789%
13980275562

Pull #9623

github

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

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

26603 existing lines in 443 files now uncovered.

96807 of 165200 relevant lines covered (58.6%)

1.82 hits per line

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

78.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/v2"
23
        "github.com/lightningnetwork/lnd/input"
24
        "github.com/lightningnetwork/lnd/lntypes"
25
        "github.com/lightningnetwork/lnd/lnutils"
26
        "github.com/lightningnetwork/lnd/lnwallet"
27
        "github.com/lightningnetwork/lnd/lnwire"
28
)
29

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

119
        return true
3✔
120
}
121

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

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

131
        return htlcSets
3✔
132
}
133

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

221
        cfg chainWatcherConfig
222

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

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

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

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

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

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

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

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

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

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

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

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

315
                c.fundingConfirmedNtfn = confNtfn
3✔
316
        }
317

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

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

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

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

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

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

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

3✔
347
        return nil
3✔
348
}
349

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

356
        close(c.quit)
3✔
357

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

3✔
360
        return nil
3✔
361
}
362

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

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

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

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

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

3✔
394
        return sub
3✔
395
}
396

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

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

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

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

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

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

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

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

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

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

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

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

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

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

521
        return true, nil
3✔
522
}
523

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

700
        return true, nil
3✔
701
}
702

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

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

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

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

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

741
                return true, nil
3✔
742

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

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

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

UNCOV
766
                return true, nil
×
767
        }
768

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

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

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

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

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

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

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

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

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

836
        return true, nil
3✔
837
}
838

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

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

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

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

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

893
        return true, nil
3✔
894
}
895

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3✔
1022
        return nil
3✔
1023
}
1024

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

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

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

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

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

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

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

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

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

3✔
1112
        return nil
3✔
1113
}
1114

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

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

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

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

3✔
1163
        return nil
3✔
1164
}
1165

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

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

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

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

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

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

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

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

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

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

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

3✔
1258
        return nil
3✔
1259
}
1260

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

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

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

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

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

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

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

1328
        return fundingPkScript, nil
3✔
1329
}
1330

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

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

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

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

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

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

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

1392
                return nil
3✔
1393
        }
1394

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

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

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

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

×
1429
        return nil
×
1430
}
1431

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

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

×
1454
                return spend
×
1455

1456
        default:
3✔
1457
        }
1458

1459
        return nil
3✔
1460
}
1461

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

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

1477
                log.Debugf("Taproot ChannelPoint(%v) confirmed", op)
3✔
1478

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

3✔
1483
                return true
3✔
1484

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

3✔
1488
                return false
3✔
1489
        }
1490
}
1491

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

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

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

3✔
1518
                return
3✔
1519
        }
3✔
1520

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