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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

56.06
/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/chainntnfs"
20
        "github.com/lightningnetwork/lnd/channeldb"
21
        "github.com/lightningnetwork/lnd/fn"
22
        "github.com/lightningnetwork/lnd/input"
23
        "github.com/lightningnetwork/lnd/lntypes"
24
        "github.com/lightningnetwork/lnd/lnutils"
25
        "github.com/lightningnetwork/lnd/lnwallet"
26
        "github.com/lightningnetwork/lnd/lnwire"
27
)
28

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

118
        return true
8✔
119
}
120

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

21✔
126
        for htlcSetKey, htlcs := range c.HtlcSets {
33✔
127
                htlcSets[htlcSetKey] = newHtlcSet(htlcs)
12✔
128
        }
12✔
129

130
        return htlcSets
21✔
131
}
132

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

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

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

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

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

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

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

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

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

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

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

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

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

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

210
        quit chan struct{}
211
        wg   sync.WaitGroup
212

213
        cfg chainWatcherConfig
214

215
        // stateHintObfuscator is a 48-bit state hint that's used to obfuscate
216
        // the current state number on the commitment transactions.
217
        stateHintObfuscator [lnwallet.StateHintSize]byte
218

219
        // fundingPkScript is the pkScript of the funding output.
220
        fundingPkScript []byte
221

222
        // heightHint is the height hint used to checkpoint scans on chain for
223
        // conf/spend events.
224
        heightHint uint32
225

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

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

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

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

×
UNCOV
260
        return &chainWatcher{
×
UNCOV
261
                cfg:                 cfg,
×
262
                stateHintObfuscator: stateHint,
263
                quit:                make(chan struct{}),
26✔
264
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
26✔
265
        }, nil
26✔
266
}
26✔
267

26✔
268
// Start starts all goroutines that the chainWatcher needs to perform its
26✔
269
// duties.
270
func (c *chainWatcher) Start() error {
271
        if !atomic.CompareAndSwapInt32(&c.started, 0, 1) {
272
                return nil
273
        }
26✔
274

26✔
UNCOV
275
        chanState := c.cfg.chanState
×
UNCOV
276
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
×
277
                chanState.FundingOutpoint)
278

26✔
279
        // First, we'll register for a notification to be dispatched if the
26✔
280
        // funding output is spent.
26✔
281
        fundingOut := &chanState.FundingOutpoint
26✔
282

26✔
283
        // As a height hint, we'll try to use the opening height, but if the
26✔
284
        // channel isn't yet open, then we'll use the height it was broadcast
26✔
285
        // at. This may be an unconfirmed zero-conf channel.
26✔
286
        c.heightHint = c.cfg.chanState.ShortChanID().BlockHeight
26✔
287
        if c.heightHint == 0 {
26✔
288
                c.heightHint = chanState.BroadcastHeight()
26✔
289
        }
26✔
290

26✔
UNCOV
291
        // Since no zero-conf state is stored in a channel backup, the below
×
UNCOV
292
        // logic will not be triggered for restored, zero-conf channels. Set
×
293
        // the height hint for zero-conf channels.
294
        if chanState.IsZeroConf() {
295
                if chanState.ZeroConfConfirmed() {
296
                        // If the zero-conf channel is confirmed, we'll use the
297
                        // confirmed SCID's block height.
26✔
UNCOV
298
                        c.heightHint = chanState.ZeroConfRealScid().BlockHeight
×
UNCOV
299
                } else {
×
UNCOV
300
                        // The zero-conf channel is unconfirmed. We'll need to
×
UNCOV
301
                        // use the FundingBroadcastHeight.
×
UNCOV
302
                        c.heightHint = chanState.BroadcastHeight()
×
UNCOV
303
                }
×
UNCOV
304
        }
×
UNCOV
305

×
UNCOV
306
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
×
307
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
308

309
        var (
26✔
310
                err error
26✔
311
        )
26✔
312
        if chanState.ChanType.IsTaproot() {
26✔
313
                c.fundingPkScript, _, err = input.GenTaprootFundingScript(
26✔
314
                        localKey, remoteKey, 0, chanState.TapscriptRoot,
26✔
315
                )
26✔
UNCOV
316
                if err != nil {
×
317
                        return err
×
318
                }
×
UNCOV
319
        } else {
×
UNCOV
320
                multiSigScript, err := input.GenMultiSigScript(
×
UNCOV
321
                        localKey.SerializeCompressed(),
×
322
                        remoteKey.SerializeCompressed(),
26✔
323
                )
26✔
324
                if err != nil {
26✔
325
                        return err
26✔
326
                }
26✔
327
                c.fundingPkScript, err = input.WitnessScriptHash(multiSigScript)
26✔
UNCOV
328
                if err != nil {
×
329
                        return err
×
330
                }
26✔
331
        }
26✔
UNCOV
332

×
UNCOV
333
        spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn(
×
334
                fundingOut, c.fundingPkScript, c.heightHint,
335
        )
336
        if err != nil {
26✔
337
                return err
26✔
338
        }
26✔
339

26✔
UNCOV
340
        // With the spend notification obtained, we'll now dispatch the
×
UNCOV
341
        // closeObserver which will properly react to any changes.
×
342
        c.wg.Add(1)
343
        go c.closeObserver(spendNtfn)
344

345
        return nil
26✔
346
}
26✔
347

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

26✔
UNCOV
354
        close(c.quit)
×
UNCOV
355

×
356
        c.wg.Wait()
357

26✔
358
        return nil
26✔
359
}
26✔
360

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

367
        c.Lock()
368
        clientID := c.clientID
26✔
369
        c.clientID++
26✔
370
        c.Unlock()
26✔
371

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

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

11✔
388
        c.Lock()
11✔
389
        c.clientSubscriptions[clientID] = sub
390
        c.Unlock()
391

26✔
392
        return sub
26✔
393
}
26✔
394

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

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

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

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

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

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

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

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

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

11✔
489
                switch {
27✔
490
                case bytes.Equal(localScript.PkScript(), pkScript):
16✔
491
                        ourCommit = true
16✔
492

16✔
493
                case bytes.Equal(remoteScript.PkScript(), pkScript):
4✔
494
                        ourCommit = true
4✔
495
                }
496
        }
4✔
497

4✔
498
        // If the script is not present, this cannot be our commit.
499
        if !ourCommit {
500
                return false, nil
501
        }
502

15✔
503
        log.Warnf("Detected local unilateral close of unknown state %v "+
4✔
504
                "(our state=%v)", broadcastStateNum,
4✔
505
                chainSet.localCommit.CommitHeight)
506

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

×
UNCOV
518
        return true, nil
×
UNCOV
519
}
×
520

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

530
        // commitSet includes information pertaining to the set of active HTLCs
531
        // on each commitment.
532
        commitSet CommitSet
533

534
        // remoteCommit is the current commitment of the remote party.
535
        remoteCommit channeldb.ChannelCommitment
536

537
        // localCommit is our current commitment.
538
        localCommit channeldb.ChannelCommitment
539

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

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

×
UNCOV
557
        log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
×
UNCOV
558
                chanState.FundingOutpoint, chanState.ChanType,
×
559
                spew.Sdump(localCommit))
560
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
15✔
561
                chanState.FundingOutpoint, chanState.ChanType,
15✔
562
                spew.Sdump(remoteCommit))
15✔
563

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

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

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

1✔
592
                htlcs := remoteChainTip.Commitment.Htlcs
1✔
593
                commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
1✔
594
        }
1✔
595

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

×
UNCOV
607
        return &chainSet{
×
UNCOV
608
                remoteStateNum:      remoteStateNum,
×
609
                commitSet:           commitSet,
610
                localCommit:         *localCommit,
15✔
611
                remoteCommit:        *remoteCommit,
15✔
612
                remotePendingCommit: remotePendingCommit,
15✔
613
        }, nil
15✔
614
}
15✔
615

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

624
        log.Infof("Close observer for ChannelPoint(%v) active",
26✔
625
                c.cfg.chanState.FundingOutpoint)
26✔
626

26✔
627
        // If this is a taproot channel, before we proceed, we want to ensure
26✔
628
        // that the expected funding output has confirmed on chain.
26✔
629
        if c.cfg.chanState.ChanType.IsTaproot() {
26✔
630
                fundingPoint := c.cfg.chanState.FundingOutpoint
26✔
631

26✔
632
                confNtfn, err := c.cfg.notifier.RegisterConfirmationsNtfn(
26✔
UNCOV
633
                        &fundingPoint.Hash, c.fundingPkScript, 1, c.heightHint,
×
UNCOV
634
                )
×
UNCOV
635
                if err != nil {
×
636
                        log.Warnf("unable to register for conf: %v", err)
×
637
                }
×
UNCOV
638

×
UNCOV
639
                log.Infof("Waiting for taproot ChannelPoint(%v) to confirm...",
×
UNCOV
640
                        c.cfg.chanState.FundingOutpoint)
×
641

UNCOV
642
                select {
×
UNCOV
643
                case _, ok := <-confNtfn.Confirmed:
×
UNCOV
644
                        // If the channel was closed, then this means that the
×
UNCOV
645
                        // notifier exited, so we will as well.
×
UNCOV
646
                        if !ok {
×
647
                                return
×
648
                        }
×
UNCOV
649
                case <-c.quit:
×
UNCOV
650
                        return
×
UNCOV
651
                }
×
UNCOV
652
        }
×
UNCOV
653

×
654
        select {
655
        // We've detected a spend of the channel onchain! Depending on the type
656
        // of spend, we'll act accordingly, so we'll examine the spending
657
        // transaction to determine what we should do.
26✔
658
        //
659
        // TODO(Roasbeef): need to be able to ensure this only triggers
660
        // on confirmation, to ensure if multiple txns are broadcast, we
661
        // act on the one that's timestamped
662
        case commitSpend, ok := <-spendNtfn.Spend:
663
                // If the channel was closed, then this means that the notifier
664
                // exited, so we will as well.
665
                if !ok {
15✔
666
                        return
15✔
667
                }
15✔
668

15✔
UNCOV
669
                // Otherwise, the remote party might have broadcast a prior
×
UNCOV
670
                // revoked state...!!!
×
671
                commitTxBroadcast := commitSpend.SpendingTx
672

673
                // First, we'll construct the chainset which includes all the
674
                // data we need to dispatch an event to our subscribers about
15✔
675
                // this possible channel close event.
15✔
676
                chainSet, err := newChainSet(c.cfg.chanState)
15✔
677
                if err != nil {
15✔
678
                        log.Errorf("unable to create commit set: %v", err)
15✔
679
                        return
15✔
680
                }
15✔
UNCOV
681

×
UNCOV
682
                // Decode the state hint encoded within the commitment
×
UNCOV
683
                // transaction to determine if this is a revoked state or not.
×
684
                obfuscator := c.stateHintObfuscator
685
                broadcastStateNum := c.cfg.extractStateNumHint(
686
                        commitTxBroadcast, obfuscator,
687
                )
15✔
688

15✔
689
                // We'll go on to check whether it could be our own commitment
15✔
690
                // that was published and know is confirmed.
15✔
691
                ok, err = c.handleKnownLocalState(
15✔
692
                        commitSpend, broadcastStateNum, chainSet,
15✔
693
                )
15✔
694
                if err != nil {
15✔
695
                        log.Errorf("Unable to handle known local state: %v",
15✔
696
                                err)
15✔
697
                        return
15✔
698
                }
×
UNCOV
699

×
UNCOV
700
                if ok {
×
UNCOV
701
                        return
×
702
                }
703

17✔
704
                // Now that we know it is neither a non-cooperative closure nor
2✔
705
                // a local close with the latest state, we check if it is the
2✔
706
                // remote that closed with any prior or current state.
707
                ok, err = c.handleKnownRemoteState(
708
                        commitSpend, broadcastStateNum, chainSet,
709
                )
710
                if err != nil {
13✔
711
                        log.Errorf("Unable to handle known remote state: %v",
13✔
712
                                err)
13✔
713
                        return
13✔
714
                }
×
UNCOV
715

×
UNCOV
716
                if ok {
×
UNCOV
717
                        return
×
718
                }
719

15✔
720
                // Next, we'll check to see if this is a cooperative channel
2✔
721
                // closure or not. This is characterized by having an input
2✔
722
                // sequence number that's finalized. This won't happen with
723
                // regular commitment transactions due to the state hint
724
                // encoding scheme.
725
                switch commitTxBroadcast.TxIn[0].Sequence {
726
                case wire.MaxTxInSequenceNum:
727
                        fallthrough
728
                case mempool.MaxRBFSequence:
11✔
UNCOV
729
                        // TODO(roasbeef): rare but possible, need itest case
×
UNCOV
730
                        // for
×
UNCOV
731
                        err := c.dispatchCooperativeClose(commitSpend)
×
UNCOV
732
                        if err != nil {
×
733
                                log.Errorf("unable to handle co op close: %v", err)
×
734
                        }
×
UNCOV
735
                        return
×
UNCOV
736
                }
×
UNCOV
737

×
UNCOV
738
                log.Warnf("Unknown commitment broadcast for "+
×
739
                        "ChannelPoint(%v) ", c.cfg.chanState.FundingOutpoint)
740

741
                // We'll try to recover as best as possible from losing state.
11✔
742
                // We first check if this was a local unknown state. This could
11✔
743
                // happen if we force close, then lose state or attempt
11✔
744
                // recovery before the commitment confirms.
11✔
745
                ok, err = c.handleUnknownLocalState(
11✔
746
                        commitSpend, broadcastStateNum, chainSet,
11✔
747
                )
11✔
748
                if err != nil {
11✔
749
                        log.Errorf("Unable to handle known local state: %v",
11✔
750
                                err)
11✔
751
                        return
11✔
752
                }
×
UNCOV
753

×
UNCOV
754
                if ok {
×
UNCOV
755
                        return
×
756
                }
757

18✔
758
                // Since it was neither a known remote state, nor a local state
7✔
759
                // that was published, it most likely mean we lost state and
7✔
760
                // the remote node closed. In this case we must start the DLP
761
                // protocol in hope of getting our money back.
762
                ok, err = c.handleUnknownRemoteState(
763
                        commitSpend, broadcastStateNum, chainSet,
764
                )
765
                if err != nil {
4✔
766
                        log.Errorf("Unable to handle unknown remote state: %v",
4✔
767
                                err)
4✔
768
                        return
4✔
769
                }
×
UNCOV
770

×
UNCOV
771
                if ok {
×
UNCOV
772
                        return
×
773
                }
774

8✔
775
                log.Warnf("Unable to handle spending tx %v of channel point %v",
4✔
776
                        commitTxBroadcast.TxHash(), c.cfg.chanState.FundingOutpoint)
4✔
777
                return
UNCOV
778

×
UNCOV
779
        // The chainWatcher has been signalled to exit, so we'll do so now.
×
UNCOV
780
        case <-c.quit:
×
781
                return
782
        }
783
}
11✔
784

11✔
785
// handleKnownLocalState checks whether the passed spend is a local state that
786
// is known to us (the current state). If so we will act on this state using
787
// the passed chainSet. If this is not a known local state, false is returned.
788
func (c *chainWatcher) handleKnownLocalState(
789
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
790
        chainSet *chainSet) (bool, error) {
791

792
        // If the channel is recovered, we won't have a local commit to check
793
        // against, so immediately return.
15✔
794
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
15✔
795
                return false, nil
15✔
796
        }
15✔
797

15✔
UNCOV
798
        commitTxBroadcast := commitSpend.SpendingTx
×
UNCOV
799
        commitHash := commitTxBroadcast.TxHash()
×
800

801
        // Check whether our latest local state hit the chain.
15✔
802
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
15✔
803
                return false, nil
15✔
804
        }
15✔
805

28✔
806
        chainSet.commitSet.ConfCommitKey = &LocalHtlcSet
13✔
807
        if err := c.dispatchLocalForceClose(
13✔
808
                commitSpend, broadcastStateNum, chainSet.commitSet,
809
        ); err != nil {
2✔
810
                return false, fmt.Errorf("unable to handle local"+
2✔
811
                        "close for chan_point=%v: %v",
2✔
812
                        c.cfg.chanState.FundingOutpoint, err)
2✔
813
        }
×
UNCOV
814

×
UNCOV
815
        return true, nil
×
UNCOV
816
}
×
817

818
// handleKnownRemoteState checks whether the passed spend is a remote state
2✔
819
// that is known to us (a revoked, current or pending state). If so we will act
820
// on this state using the passed chainSet. If this is not a known remote
821
// state, false is returned.
822
func (c *chainWatcher) handleKnownRemoteState(
823
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
824
        chainSet *chainSet) (bool, error) {
825

826
        // If the channel is recovered, we won't have any remote commit to
827
        // check against, so imemdiately return.
13✔
828
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
13✔
829
                return false, nil
13✔
830
        }
13✔
831

13✔
UNCOV
832
        commitTxBroadcast := commitSpend.SpendingTx
×
UNCOV
833
        commitHash := commitTxBroadcast.TxHash()
×
834

835
        switch {
13✔
836
        // If the spending transaction matches the current latest state, then
13✔
837
        // they've initiated a unilateral close. So we'll trigger the
13✔
838
        // unilateral close signal so subscribers can clean up the state as
13✔
839
        // necessary.
840
        case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
841
                log.Infof("Remote party broadcast base set, "+
842
                        "commit_num=%v", chainSet.remoteStateNum)
843

1✔
844
                chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
1✔
845
                err := c.dispatchRemoteForceClose(
1✔
846
                        commitSpend, chainSet.remoteCommit,
1✔
847
                        chainSet.commitSet,
1✔
848
                        c.cfg.chanState.RemoteCurrentRevocation,
1✔
849
                )
1✔
850
                if err != nil {
1✔
851
                        return false, fmt.Errorf("unable to handle remote "+
1✔
852
                                "close for chan_point=%v: %v",
1✔
853
                                c.cfg.chanState.FundingOutpoint, err)
1✔
854
                }
×
UNCOV
855

×
UNCOV
856
                return true, nil
×
UNCOV
857

×
858
        // We'll also handle the case of the remote party broadcasting
859
        // their commitment transaction which is one height above ours.
1✔
860
        // This case can arise when we initiate a state transition, but
861
        // the remote party has a fail crash _after_ accepting the new
862
        // state, but _before_ sending their signature to us.
863
        case chainSet.remotePendingCommit != nil &&
864
                chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
865

866
                log.Infof("Remote party broadcast pending set, "+
867
                        "commit_num=%v", chainSet.remoteStateNum+1)
1✔
868

1✔
869
                chainSet.commitSet.ConfCommitKey = &RemotePendingHtlcSet
1✔
870
                err := c.dispatchRemoteForceClose(
1✔
871
                        commitSpend, *chainSet.remotePendingCommit,
1✔
872
                        chainSet.commitSet,
1✔
873
                        c.cfg.chanState.RemoteNextRevocation,
1✔
874
                )
1✔
875
                if err != nil {
1✔
876
                        return false, fmt.Errorf("unable to handle remote "+
1✔
877
                                "close for chan_point=%v: %v",
1✔
878
                                c.cfg.chanState.FundingOutpoint, err)
1✔
879
                }
×
UNCOV
880

×
UNCOV
881
                return true, nil
×
UNCOV
882
        }
×
883

884
        // This is neither a remote force close or a "future" commitment, we
1✔
885
        // now check whether it's a remote breach and properly handle it.
886
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
887
}
888

889
// handlePossibleBreach checks whether the remote has breached and dispatches a
11✔
890
// breach resolution to claim funds.
891
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
892
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
893

894
        // We check if we have a revoked state at this state num that matches
895
        // the spend transaction.
11✔
896
        spendHeight := uint32(commitSpend.SpendingHeight)
11✔
897
        retribution, err := lnwallet.NewBreachRetribution(
11✔
898
                c.cfg.chanState, broadcastStateNum, spendHeight,
11✔
899
                commitSpend.SpendingTx, c.cfg.auxLeafStore,
11✔
900
        )
11✔
901

11✔
902
        switch {
11✔
903
        // If we had no log entry at this height, this was not a revoked state.
11✔
904
        case err == channeldb.ErrLogEntryNotFound:
11✔
905
                return false, nil
11✔
906
        case err == channeldb.ErrNoPastDeltas:
907
                return false, nil
8✔
908

8✔
909
        case err != nil:
3✔
910
                return false, fmt.Errorf("unable to create breach "+
3✔
911
                        "retribution: %v", err)
UNCOV
912
        }
×
UNCOV
913

×
UNCOV
914
        // We found a revoked state at this height, but it could still be our
×
915
        // own broadcasted state we are looking at. Therefore check that the
916
        // commit matches before assuming it was a breach.
917
        commitHash := commitSpend.SpendingTx.TxHash()
918
        if retribution.BreachTxHash != commitHash {
919
                return false, nil
920
        }
×
UNCOV
921

×
UNCOV
922
        // Create an AnchorResolution for the breached state.
×
UNCOV
923
        anchorRes, err := lnwallet.NewAnchorResolution(
×
924
                c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
925
                lntypes.Remote,
UNCOV
926
        )
×
UNCOV
927
        if err != nil {
×
928
                return false, fmt.Errorf("unable to create anchor "+
×
929
                        "resolution: %v", err)
×
930
        }
×
UNCOV
931

×
UNCOV
932
        // We'll set the ConfCommitKey here as the remote htlc set. This is
×
UNCOV
933
        // only used to ensure a nil-pointer-dereference doesn't occur and is
×
934
        // not used otherwise. The HTLC's may not exist for the
935
        // RemotePendingHtlcSet.
936
        chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
937

938
        // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
UNCOV
939
        // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
×
UNCOV
940
        // broadcast to allow subscribers to swiftly dispatch justice!!!
×
UNCOV
941
        err = c.dispatchContractBreach(
×
UNCOV
942
                commitSpend, chainSet, broadcastStateNum, retribution,
×
UNCOV
943
                anchorRes,
×
UNCOV
944
        )
×
UNCOV
945
        if err != nil {
×
946
                return false, fmt.Errorf("unable to handle channel "+
×
947
                        "breach for chan_point=%v: %v",
×
948
                        c.cfg.chanState.FundingOutpoint, err)
×
949
        }
×
UNCOV
950

×
UNCOV
951
        return true, nil
×
UNCOV
952
}
×
953

UNCOV
954
// handleUnknownRemoteState is the last attempt we make at reclaiming funds
×
955
// from the closed channel, by checkin whether the passed spend _could_ be a
956
// remote spend that is unknown to us (we lost state). We will try to initiate
957
// Data Loss Protection in order to restore our commit point and reclaim our
958
// funds from the channel. If we are not able to act on it, false is returned.
959
func (c *chainWatcher) handleUnknownRemoteState(
960
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
961
        chainSet *chainSet) (bool, error) {
962

963
        log.Warnf("Remote node broadcast state #%v, "+
964
                "which is more than 1 beyond best known "+
4✔
965
                "state #%v!!! Attempting recovery...",
4✔
966
                broadcastStateNum, chainSet.remoteStateNum)
4✔
967

4✔
968
        // If this isn't a tweakless commitment, then we'll need to wait for
4✔
969
        // the remote party's latest unrevoked commitment point to be presented
4✔
970
        // to us as we need this to sweep. Otherwise, we can dispatch the
4✔
971
        // remote close and sweep immediately using a fake commitPoint as it
4✔
972
        // isn't actually needed for recovery anymore.
4✔
973
        commitPoint := c.cfg.chanState.RemoteCurrentRevocation
4✔
974
        tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
4✔
975
        if !tweaklessCommit {
4✔
976
                commitPoint = c.waitForCommitmentPoint()
4✔
977
                if commitPoint == nil {
4✔
978
                        return false, fmt.Errorf("unable to get commit point")
8✔
979
                }
4✔
980

4✔
UNCOV
981
                log.Infof("Recovered commit point(%x) for "+
×
UNCOV
982
                        "channel(%v)! Now attempting to use it to "+
×
983
                        "sweep our funds...",
984
                        commitPoint.SerializeCompressed(),
4✔
985
                        c.cfg.chanState.FundingOutpoint)
4✔
986
        } else {
4✔
987
                log.Infof("ChannelPoint(%v) is tweakless, "+
4✔
988
                        "moving to sweep directly on chain",
4✔
UNCOV
989
                        c.cfg.chanState.FundingOutpoint)
×
UNCOV
990
        }
×
UNCOV
991

×
UNCOV
992
        // Since we don't have the commitment stored for this state, we'll just
×
UNCOV
993
        // pass an empty commitment within the commitment set. Note that this
×
994
        // means we won't be able to recover any HTLC funds.
995
        //
996
        // TODO(halseth): can we try to recover some HTLCs?
997
        chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
998
        err := c.dispatchRemoteForceClose(
999
                commitSpend, channeldb.ChannelCommitment{},
1000
                chainSet.commitSet, commitPoint,
4✔
1001
        )
4✔
1002
        if err != nil {
4✔
1003
                return false, fmt.Errorf("unable to handle remote "+
4✔
1004
                        "close for chan_point=%v: %v",
4✔
1005
                        c.cfg.chanState.FundingOutpoint, err)
4✔
1006
        }
×
UNCOV
1007

×
UNCOV
1008
        return true, nil
×
UNCOV
1009
}
×
1010

1011
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
4✔
1012
// to a script that the wallet controls or the channel defines as its delivery
1013
// script . If no outputs pay to us (determined by these criteria), then we
1014
// return zero. This is possible as our output may have been trimmed due to
1015
// being dust.
1016
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
1017
        // There are two main cases we have to handle here. First, in the coop
1018
        // close case we will always have saved the delivery address we used
UNCOV
1019
        // whether it was from the upfront shutdown, from the delivery address
×
UNCOV
1020
        // requested at close time, or even an automatically generated one. All
×
UNCOV
1021
        // coop-close cases can be identified in the following manner:
×
UNCOV
1022
        shutdown, _ := c.cfg.chanState.ShutdownInfo()
×
UNCOV
1023
        oDeliveryAddr := fn.MapOption(
×
UNCOV
1024
                func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
×
UNCOV
1025
                        return i.DeliveryScript.Val
×
UNCOV
1026
                })(shutdown)
×
UNCOV
1027

×
UNCOV
1028
        // Here we define a function capable of identifying whether an output
×
UNCOV
1029
        // corresponds with our local delivery script from a ShutdownInfo if we
×
1030
        // have a ShutdownInfo for this chainWatcher's underlying channel.
1031
        //
1032
        // isDeliveryOutput :: *TxOut -> bool
1033
        isDeliveryOutput := func(o *wire.TxOut) bool {
1034
                return fn.ElimOption(
1035
                        oDeliveryAddr,
UNCOV
1036
                        // If we don't have a delivery addr, then the output
×
UNCOV
1037
                        // can't match it.
×
UNCOV
1038
                        func() bool { return false },
×
UNCOV
1039
                        // Otherwise if the PkScript of the TxOut matches our
×
UNCOV
1040
                        // delivery script then this is a delivery output.
×
UNCOV
1041
                        func(a lnwire.DeliveryAddress) bool {
×
1042
                                return slices.Equal(a, o.PkScript)
1043
                        },
UNCOV
1044
                )
×
UNCOV
1045
        }
×
UNCOV
1046

×
1047
        // Here we define a function capable of identifying whether an output
1048
        // belongs to the LND wallet. We use this as a heuristic in the case
1049
        // where we might be looking for spendable force closure outputs.
1050
        //
1051
        // isWalletOutput :: *TxOut -> bool
1052
        isWalletOutput := func(out *wire.TxOut) bool {
1053
                _, addrs, _, err := txscript.ExtractPkScriptAddrs(
1054
                        // Doesn't matter what net we actually pass in.
UNCOV
1055
                        out.PkScript, &chaincfg.TestNet3Params,
×
UNCOV
1056
                )
×
UNCOV
1057
                if err != nil {
×
1058
                        return false
×
1059
                }
×
UNCOV
1060

×
UNCOV
1061
                return fn.Any(c.cfg.isOurAddr, addrs)
×
UNCOV
1062
        }
×
1063

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

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

×
UNCOV
1071
        // Return the sum.
×
UNCOV
1072
        return btcutil.Amount(fn.Sum(vals))
×
1073
}
1074

UNCOV
1075
// dispatchCooperativeClose processed a detect cooperative channel closure.
×
1076
// We'll use the spending transaction to locate our output within the
1077
// transaction, then clean up the database state. We'll also dispatch a
1078
// notification to all subscribers that the channel has been closed in this
1079
// manner.
1080
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
1081
        broadcastTx := commitSpend.SpendingTx
1082

UNCOV
1083
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
×
UNCOV
1084
                c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
×
UNCOV
1085

×
UNCOV
1086
        // If the input *is* final, then we'll check to see which output is
×
UNCOV
1087
        // ours.
×
UNCOV
1088
        localAmt := c.toSelfAmount(broadcastTx)
×
UNCOV
1089

×
UNCOV
1090
        // Once this is known, we'll mark the state as fully closed in the
×
UNCOV
1091
        // database. We can do this as a cooperatively closed channel has all
×
UNCOV
1092
        // its outputs resolved after only one confirmation.
×
UNCOV
1093
        closeSummary := &channeldb.ChannelCloseSummary{
×
UNCOV
1094
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
×
UNCOV
1095
                ChainHash:               c.cfg.chanState.ChainHash,
×
UNCOV
1096
                ClosingTXID:             *commitSpend.SpenderTxHash,
×
UNCOV
1097
                RemotePub:               c.cfg.chanState.IdentityPub,
×
UNCOV
1098
                Capacity:                c.cfg.chanState.Capacity,
×
UNCOV
1099
                CloseHeight:             uint32(commitSpend.SpendingHeight),
×
UNCOV
1100
                SettledBalance:          localAmt,
×
UNCOV
1101
                CloseType:               channeldb.CooperativeClose,
×
UNCOV
1102
                ShortChanID:             c.cfg.chanState.ShortChanID(),
×
UNCOV
1103
                IsPending:               true,
×
UNCOV
1104
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
×
UNCOV
1105
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
×
UNCOV
1106
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
UNCOV
1107
        }
×
UNCOV
1108

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

×
UNCOV
1118
        // Create a summary of all the information needed to handle the
×
UNCOV
1119
        // cooperative closure.
×
1120
        closeInfo := &CooperativeCloseInfo{
1121
                ChannelCloseSummary: closeSummary,
1122
        }
UNCOV
1123

×
UNCOV
1124
        // With the event processed, we'll now notify all subscribers of the
×
UNCOV
1125
        // event.
×
UNCOV
1126
        c.Lock()
×
UNCOV
1127
        for _, sub := range c.clientSubscriptions {
×
UNCOV
1128
                select {
×
UNCOV
1129
                case sub.CooperativeClosure <- closeInfo:
×
1130
                case <-c.quit:
×
1131
                        c.Unlock()
×
1132
                        return fmt.Errorf("exiting")
×
UNCOV
1133
                }
×
UNCOV
1134
        }
×
UNCOV
1135
        c.Unlock()
×
1136

1137
        return nil
UNCOV
1138
}
×
UNCOV
1139

×
UNCOV
1140
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
×
1141
func (c *chainWatcher) dispatchLocalForceClose(
1142
        commitSpend *chainntnfs.SpendDetail,
1143
        stateNum uint64, commitSet CommitSet) error {
1144

1145
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
1146
                "detected", c.cfg.chanState.FundingOutpoint)
9✔
1147

9✔
1148
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
9✔
1149
                c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
9✔
1150
                c.cfg.auxLeafStore,
9✔
1151
        )
9✔
1152
        if err != nil {
9✔
1153
                return err
9✔
1154
        }
9✔
1155

9✔
UNCOV
1156
        // As we've detected that the channel has been closed, immediately
×
UNCOV
1157
        // creating a close summary for future usage by related sub-systems.
×
1158
        chanSnapshot := forceClose.ChanSnapshot
1159
        closeSummary := &channeldb.ChannelCloseSummary{
1160
                ChanPoint:               chanSnapshot.ChannelPoint,
1161
                ChainHash:               chanSnapshot.ChainHash,
9✔
1162
                ClosingTXID:             forceClose.CloseTx.TxHash(),
9✔
1163
                RemotePub:               &chanSnapshot.RemoteIdentity,
9✔
1164
                Capacity:                chanSnapshot.Capacity,
9✔
1165
                CloseType:               channeldb.LocalForceClose,
9✔
1166
                IsPending:               true,
9✔
1167
                ShortChanID:             c.cfg.chanState.ShortChanID(),
9✔
1168
                CloseHeight:             uint32(commitSpend.SpendingHeight),
9✔
1169
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
9✔
1170
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
9✔
1171
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
9✔
1172
        }
9✔
1173

9✔
1174
        // If our commitment output isn't dust or we have active HTLC's on the
9✔
1175
        // commitment transaction, then we'll populate the balances on the
9✔
1176
        // close channel summary.
9✔
1177
        if forceClose.CommitResolution != nil {
9✔
1178
                closeSummary.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis()
9✔
1179
                closeSummary.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis()
9✔
1180
        }
15✔
1181
        for _, htlc := range forceClose.HtlcResolutions.OutgoingHTLCs {
6✔
1182
                htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value)
6✔
1183
                closeSummary.TimeLockedBalance += htlcValue
6✔
1184
        }
9✔
UNCOV
1185

×
UNCOV
1186
        // Attempt to add a channel sync message to the close summary.
×
UNCOV
1187
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
×
1188
        if err != nil {
1189
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
1190
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
9✔
1191
        } else {
9✔
UNCOV
1192
                closeSummary.LastChanSyncMsg = chanSync
×
UNCOV
1193
        }
×
1194

9✔
1195
        // With the event processed, we'll now notify all subscribers of the
9✔
1196
        // event.
9✔
1197
        closeInfo := &LocalUnilateralCloseInfo{
1198
                SpendDetail:            commitSpend,
1199
                LocalForceCloseSummary: forceClose,
1200
                ChannelCloseSummary:    closeSummary,
9✔
1201
                CommitSet:              commitSet,
9✔
1202
        }
9✔
1203
        c.Lock()
9✔
1204
        for _, sub := range c.clientSubscriptions {
9✔
1205
                select {
9✔
1206
                case sub.LocalUnilateralClosure <- closeInfo:
9✔
1207
                case <-c.quit:
18✔
1208
                        c.Unlock()
9✔
1209
                        return fmt.Errorf("exiting")
9✔
UNCOV
1210
                }
×
UNCOV
1211
        }
×
UNCOV
1212
        c.Unlock()
×
1213

1214
        return nil
1215
}
9✔
1216

9✔
1217
// dispatchRemoteForceClose processes a detected unilateral channel closure by
9✔
1218
// the remote party. This function will prepare a UnilateralCloseSummary which
1219
// will then be sent to any subscribers allowing them to resolve all our funds
1220
// in the channel on chain. Once this close summary is prepared, all registered
1221
// subscribers will receive a notification of this event. The commitPoint
1222
// argument should be set to the per_commitment_point corresponding to the
1223
// spending commitment.
1224
//
1225
// NOTE: The remoteCommit argument should be set to the stored commitment for
1226
// this particular state. If we don't have the commitment stored (should only
1227
// happen in case we have lost state) it should be set to an empty struct, in
1228
// which case we will attempt to sweep the non-HTLC output using the passed
1229
// commitPoint.
1230
func (c *chainWatcher) dispatchRemoteForceClose(
1231
        commitSpend *chainntnfs.SpendDetail,
1232
        remoteCommit channeldb.ChannelCommitment,
1233
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
1234

1235
        log.Infof("Unilateral close of ChannelPoint(%v) "+
1236
                "detected", c.cfg.chanState.FundingOutpoint)
6✔
1237

6✔
1238
        // First, we'll create a closure summary that contains all the
6✔
1239
        // materials required to let each subscriber sweep the funds in the
6✔
1240
        // channel on-chain.
6✔
1241
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
6✔
1242
                c.cfg.chanState, c.cfg.signer, commitSpend,
6✔
1243
                remoteCommit, commitPoint, c.cfg.auxLeafStore,
6✔
1244
        )
6✔
1245
        if err != nil {
6✔
1246
                return err
6✔
1247
        }
6✔
1248

6✔
UNCOV
1249
        // With the event processed, we'll now notify all subscribers of the
×
UNCOV
1250
        // event.
×
1251
        c.Lock()
1252
        for _, sub := range c.clientSubscriptions {
1253
                select {
1254
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
6✔
1255
                        UnilateralCloseSummary: uniClose,
12✔
1256
                        CommitSet:              commitSet,
6✔
1257
                }:
1258
                case <-c.quit:
1259
                        c.Unlock()
1260
                        return fmt.Errorf("exiting")
6✔
UNCOV
1261
                }
×
UNCOV
1262
        }
×
UNCOV
1263
        c.Unlock()
×
1264

1265
        return nil
1266
}
6✔
1267

6✔
1268
// dispatchContractBreach processes a detected contract breached by the remote
6✔
1269
// party. This method is to be called once we detect that the remote party has
1270
// broadcast a prior revoked commitment state. This method well prepare all the
1271
// materials required to bring the cheater to justice, then notify all
1272
// registered subscribers of this event.
1273
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
1274
        chainSet *chainSet, broadcastStateNum uint64,
1275
        retribution *lnwallet.BreachRetribution,
1276
        anchorRes *lnwallet.AnchorResolution) error {
1277

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

×
UNCOV
1282
        if err := c.cfg.chanState.MarkBorked(); err != nil {
×
1283
                return fmt.Errorf("unable to mark channel as borked: %w", err)
×
1284
        }
×
UNCOV
1285

×
UNCOV
1286
        spendHeight := uint32(spendEvent.SpendingHeight)
×
UNCOV
1287

×
1288
        log.Debugf("Punishment breach retribution created: %v",
UNCOV
1289
                lnutils.NewLogClosure(func() string {
×
UNCOV
1290
                        retribution.KeyRing.LocalHtlcKey = nil
×
UNCOV
1291
                        retribution.KeyRing.RemoteHtlcKey = nil
×
UNCOV
1292
                        retribution.KeyRing.ToLocalKey = nil
×
UNCOV
1293
                        retribution.KeyRing.ToRemoteKey = nil
×
UNCOV
1294
                        retribution.KeyRing.RevocationKey = nil
×
UNCOV
1295
                        return spew.Sdump(retribution)
×
UNCOV
1296
                }))
×
UNCOV
1297

×
UNCOV
1298
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
×
UNCOV
1299
        closeSummary := channeldb.ChannelCloseSummary{
×
1300
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
UNCOV
1301
                ChainHash:               c.cfg.chanState.ChainHash,
×
UNCOV
1302
                ClosingTXID:             *spendEvent.SpenderTxHash,
×
UNCOV
1303
                CloseHeight:             spendHeight,
×
UNCOV
1304
                RemotePub:               c.cfg.chanState.IdentityPub,
×
UNCOV
1305
                Capacity:                c.cfg.chanState.Capacity,
×
UNCOV
1306
                SettledBalance:          settledBalance,
×
UNCOV
1307
                CloseType:               channeldb.BreachClose,
×
UNCOV
1308
                IsPending:               true,
×
UNCOV
1309
                ShortChanID:             c.cfg.chanState.ShortChanID(),
×
UNCOV
1310
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
×
UNCOV
1311
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
×
UNCOV
1312
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
×
UNCOV
1313
        }
×
UNCOV
1314

×
UNCOV
1315
        // Attempt to add a channel sync message to the close summary.
×
UNCOV
1316
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
×
UNCOV
1317
        if err != nil {
×
1318
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1319
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
UNCOV
1320
        } else {
×
UNCOV
1321
                closeSummary.LastChanSyncMsg = chanSync
×
UNCOV
1322
        }
×
UNCOV
1323

×
UNCOV
1324
        // Hand the retribution info over to the BreachArbitrator. This function
×
UNCOV
1325
        // will wait for a response from the breach arbiter and then proceed to
×
1326
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1327
        // will then mark the channel as closed after resolutions and the
1328
        // commit set are logged in the arbitrator log.
1329
        if err := c.cfg.contractBreach(retribution); err != nil {
1330
                log.Errorf("unable to hand breached contract off to "+
1331
                        "BreachArbitrator: %v", err)
1332
                return err
×
1333
        }
×
UNCOV
1334

×
UNCOV
1335
        breachRes := &BreachResolution{
×
UNCOV
1336
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
×
1337
        }
UNCOV
1338

×
UNCOV
1339
        breachInfo := &BreachCloseInfo{
×
UNCOV
1340
                CommitHash:       spendEvent.SpendingTx.TxHash(),
×
UNCOV
1341
                BreachResolution: breachRes,
×
UNCOV
1342
                AnchorResolution: anchorRes,
×
UNCOV
1343
                CommitSet:        chainSet.commitSet,
×
UNCOV
1344
                CloseSummary:     closeSummary,
×
UNCOV
1345
        }
×
UNCOV
1346

×
UNCOV
1347
        // With the event processed and channel closed, we'll now notify all
×
UNCOV
1348
        // subscribers of the event.
×
UNCOV
1349
        c.Lock()
×
UNCOV
1350
        for _, sub := range c.clientSubscriptions {
×
UNCOV
1351
                select {
×
UNCOV
1352
                case sub.ContractBreach <- breachInfo:
×
1353
                case <-c.quit:
×
1354
                        c.Unlock()
×
1355
                        return fmt.Errorf("quitting")
×
UNCOV
1356
                }
×
UNCOV
1357
        }
×
UNCOV
1358
        c.Unlock()
×
1359

1360
        return nil
UNCOV
1361
}
×
UNCOV
1362

×
UNCOV
1363
// waitForCommitmentPoint waits for the commitment point to be inserted into
×
1364
// the local database. We'll use this method in the DLP case, to wait for the
1365
// remote party to send us their point, as we can't proceed until we have that.
1366
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
1367
        // If we are lucky, the remote peer sent us the correct commitment
1368
        // point during channel sync, such that we can sweep our funds. If we
1369
        // cannot find the commit point, there's not much we can do other than
4✔
1370
        // wait for us to retrieve it. We will attempt to retrieve it from the
4✔
1371
        // peer each time we connect to it.
4✔
1372
        //
4✔
1373
        // TODO(halseth): actively initiate re-connection to the peer?
4✔
1374
        backoff := minCommitPointPollTimeout
4✔
1375
        for {
4✔
1376
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
4✔
1377
                if err == nil {
4✔
1378
                        return commitPoint
8✔
1379
                }
4✔
1380

8✔
1381
                log.Errorf("Unable to retrieve commitment point for "+
4✔
1382
                        "channel(%v) with lost state: %v. Retrying in %v.",
4✔
1383
                        c.cfg.chanState.FundingOutpoint, err, backoff)
1384

×
1385
                select {
×
UNCOV
1386
                // Wait before retrying, with an exponential backoff.
×
1387
                case <-time.After(backoff):
×
1388
                        backoff = 2 * backoff
×
1389
                        if backoff > maxCommitPointPollTimeout {
1390
                                backoff = maxCommitPointPollTimeout
×
1391
                        }
×
UNCOV
1392

×
1393
                case <-c.quit:
×
1394
                        return nil
×
1395
                }
UNCOV
1396
        }
×
UNCOV
1397
}
×
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