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

lightningnetwork / lnd / 10189747109

01 Aug 2024 12:31AM UTC coverage: 58.641% (+0.2%) from 58.459%
10189747109

push

github

web-flow
Merge pull request #8949 from ProofOfKeags/fn/req

[MICRO]: fn: Add new Req type to abstract the pattern of remote processing.

125217 of 213532 relevant lines covered (58.64%)

29382.04 hits per line

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

83.27
/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 {
20✔
108
        if c == nil {
20✔
109
                return true
×
110
        }
×
111

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

118
        return true
12✔
119
}
120

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

25✔
126
        for htlcSetKey, htlcs := range c.HtlcSets {
41✔
127
                htlcSets[htlcSetKey] = newHtlcSet(htlcs)
16✔
128
        }
16✔
129

130
        return htlcSets
25✔
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

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

207
        quit chan struct{}
208
        wg   sync.WaitGroup
209

210
        cfg chainWatcherConfig
211

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

216
        // fundingPkScript is the pkScript of the funding output.
217
        fundingPkScript []byte
218

219
        // heightHint is the height hint used to checkpoint scans on chain for
220
        // conf/spend events.
221
        heightHint uint32
222

223
        // All the fields below are protected by this mutex.
224
        sync.Mutex
225

226
        // clientID is an ephemeral counter used to keep track of each
227
        // individual client subscription.
228
        clientID uint64
229

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

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

257
        return &chainWatcher{
30✔
258
                cfg:                 cfg,
30✔
259
                stateHintObfuscator: stateHint,
30✔
260
                quit:                make(chan struct{}),
30✔
261
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
30✔
262
        }, nil
30✔
263
}
264

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

272
        chanState := c.cfg.chanState
30✔
273
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
30✔
274
                chanState.FundingOutpoint)
30✔
275

30✔
276
        // First, we'll register for a notification to be dispatched if the
30✔
277
        // funding output is spent.
30✔
278
        fundingOut := &chanState.FundingOutpoint
30✔
279

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

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

303
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
30✔
304
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
30✔
305

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

330
        spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn(
30✔
331
                fundingOut, c.fundingPkScript, c.heightHint,
30✔
332
        )
30✔
333
        if err != nil {
30✔
334
                return err
×
335
        }
×
336

337
        // With the spend notification obtained, we'll now dispatch the
338
        // closeObserver which will properly react to any changes.
339
        c.wg.Add(1)
30✔
340
        go c.closeObserver(spendNtfn)
30✔
341

30✔
342
        return nil
30✔
343
}
344

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

351
        close(c.quit)
30✔
352

30✔
353
        c.wg.Wait()
30✔
354

30✔
355
        return nil
30✔
356
}
357

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

30✔
364
        c.Lock()
30✔
365
        clientID := c.clientID
30✔
366
        c.clientID++
30✔
367
        c.Unlock()
30✔
368

30✔
369
        log.Debugf("New ChainEventSubscription(id=%v) for ChannelPoint(%v)",
30✔
370
                clientID, c.cfg.chanState.FundingOutpoint)
30✔
371

30✔
372
        sub := &ChainEventSubscription{
30✔
373
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
30✔
374
                RemoteUnilateralClosure: make(chan *RemoteUnilateralCloseInfo, 1),
30✔
375
                LocalUnilateralClosure:  make(chan *LocalUnilateralCloseInfo, 1),
30✔
376
                CooperativeClosure:      make(chan *CooperativeCloseInfo, 1),
30✔
377
                ContractBreach:          make(chan *BreachCloseInfo, 1),
30✔
378
                Cancel: func() {
45✔
379
                        c.Lock()
15✔
380
                        delete(c.clientSubscriptions, clientID)
15✔
381
                        c.Unlock()
15✔
382
                },
15✔
383
        }
384

385
        c.Lock()
30✔
386
        c.clientSubscriptions[clientID] = sub
30✔
387
        c.Unlock()
30✔
388

30✔
389
        return sub
30✔
390
}
391

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

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

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

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

15✔
426
        // With the keys derived, we'll construct the remote script that'll be
15✔
427
        // present if they have a non-dust balance on the commitment.
15✔
428
        var leaseExpiry uint32
15✔
429
        if c.cfg.chanState.ChanType.HasLeaseExpiration() {
19✔
430
                leaseExpiry = c.cfg.chanState.ThawHeight
4✔
431
        }
4✔
432
        remoteScript, _, err := lnwallet.CommitScriptToRemote(
15✔
433
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
15✔
434
                commitKeyRing.ToRemoteKey, leaseExpiry,
15✔
435
        )
15✔
436
        if err != nil {
15✔
437
                return false, err
×
438
        }
×
439

440
        // Next, we'll derive our script that includes the revocation base for
441
        // the remote party allowing them to claim this output before the CSV
442
        // delay if we breach.
443
        localScript, err := lnwallet.CommitScriptToSelf(
15✔
444
                c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
15✔
445
                commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
15✔
446
                uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
15✔
447
        )
15✔
448
        if err != nil {
15✔
449
                return false, err
×
450
        }
×
451

452
        // With all our scripts assembled, we'll examine the outputs of the
453
        // commitment transaction to determine if this is a local force close
454
        // or not.
455
        ourCommit := false
15✔
456
        for _, output := range commitSpend.SpendingTx.TxOut {
35✔
457
                pkScript := output.PkScript
20✔
458

20✔
459
                switch {
20✔
460
                case bytes.Equal(localScript.PkScript(), pkScript):
8✔
461
                        ourCommit = true
8✔
462

463
                case bytes.Equal(remoteScript.PkScript(), pkScript):
8✔
464
                        ourCommit = true
8✔
465
                }
466
        }
467

468
        // If the script is not present, this cannot be our commit.
469
        if !ourCommit {
23✔
470
                return false, nil
8✔
471
        }
8✔
472

473
        log.Warnf("Detected local unilateral close of unknown state %v "+
11✔
474
                "(our state=%v)", broadcastStateNum,
11✔
475
                chainSet.localCommit.CommitHeight)
11✔
476

11✔
477
        // If this is our commitment transaction, then we try to act even
11✔
478
        // though we won't be able to sweep HTLCs.
11✔
479
        chainSet.commitSet.ConfCommitKey = &LocalHtlcSet
11✔
480
        if err := c.dispatchLocalForceClose(
11✔
481
                commitSpend, broadcastStateNum, chainSet.commitSet,
11✔
482
        ); err != nil {
11✔
483
                return false, fmt.Errorf("unable to handle local"+
×
484
                        "close for chan_point=%v: %v",
×
485
                        c.cfg.chanState.FundingOutpoint, err)
×
486
        }
×
487

488
        return true, nil
11✔
489
}
490

491
// chainSet includes all the information we need to dispatch a channel close
492
// event to any subscribers.
493
type chainSet struct {
494
        // remoteStateNum is the commitment number of the lowest valid
495
        // commitment the remote party holds from our PoV. This value is used
496
        // to determine if the remote party is playing a state that's behind,
497
        // in line, or ahead of the latest state we know for it.
498
        remoteStateNum uint64
499

500
        // commitSet includes information pertaining to the set of active HTLCs
501
        // on each commitment.
502
        commitSet CommitSet
503

504
        // remoteCommit is the current commitment of the remote party.
505
        remoteCommit channeldb.ChannelCommitment
506

507
        // localCommit is our current commitment.
508
        localCommit channeldb.ChannelCommitment
509

510
        // remotePendingCommit points to the dangling commitment of the remote
511
        // party, if it exists. If there's no dangling commitment, then this
512
        // pointer will be nil.
513
        remotePendingCommit *channeldb.ChannelCommitment
514
}
515

516
// newChainSet creates a new chainSet given the current up to date channel
517
// state.
518
func newChainSet(chanState *channeldb.OpenChannel) (*chainSet, error) {
19✔
519
        // First, we'll grab the current unrevoked commitments for ourselves
19✔
520
        // and the remote party.
19✔
521
        localCommit, remoteCommit, err := chanState.LatestCommitments()
19✔
522
        if err != nil {
19✔
523
                return nil, fmt.Errorf("unable to fetch channel state for "+
×
524
                        "chan_point=%v", chanState.FundingOutpoint)
×
525
        }
×
526

527
        log.Tracef("ChannelPoint(%v): local_commit_type=%v, local_commit=%v",
19✔
528
                chanState.FundingOutpoint, chanState.ChanType,
19✔
529
                spew.Sdump(localCommit))
19✔
530
        log.Tracef("ChannelPoint(%v): remote_commit_type=%v, remote_commit=%v",
19✔
531
                chanState.FundingOutpoint, chanState.ChanType,
19✔
532
                spew.Sdump(remoteCommit))
19✔
533

19✔
534
        // Fetch the current known commit height for the remote party, and
19✔
535
        // their pending commitment chain tip if it exists.
19✔
536
        remoteStateNum := remoteCommit.CommitHeight
19✔
537
        remoteChainTip, err := chanState.RemoteCommitChainTip()
19✔
538
        if err != nil && err != channeldb.ErrNoPendingCommit {
19✔
539
                return nil, fmt.Errorf("unable to obtain chain tip for "+
×
540
                        "ChannelPoint(%v): %v",
×
541
                        chanState.FundingOutpoint, err)
×
542
        }
×
543

544
        // Now that we have all the possible valid commitments, we'll make the
545
        // CommitSet the ChannelArbitrator will need in order to carry out its
546
        // duty.
547
        commitSet := CommitSet{
19✔
548
                HtlcSets: map[HtlcSetKey][]channeldb.HTLC{
19✔
549
                        LocalHtlcSet:  localCommit.Htlcs,
19✔
550
                        RemoteHtlcSet: remoteCommit.Htlcs,
19✔
551
                },
19✔
552
        }
19✔
553

19✔
554
        var remotePendingCommit *channeldb.ChannelCommitment
19✔
555
        if remoteChainTip != nil {
24✔
556
                remotePendingCommit = &remoteChainTip.Commitment
5✔
557
                log.Tracef("ChannelPoint(%v): remote_pending_commit_type=%v, "+
5✔
558
                        "remote_pending_commit=%v", chanState.FundingOutpoint,
5✔
559
                        chanState.ChanType,
5✔
560
                        spew.Sdump(remoteChainTip.Commitment))
5✔
561

5✔
562
                htlcs := remoteChainTip.Commitment.Htlcs
5✔
563
                commitSet.HtlcSets[RemotePendingHtlcSet] = htlcs
5✔
564
        }
5✔
565

566
        // We'll now retrieve the latest state of the revocation store so we
567
        // can populate the revocation information within the channel state
568
        // object that we have.
569
        //
570
        // TODO(roasbeef): mutation is bad mkay
571
        _, err = chanState.RemoteRevocationStore()
19✔
572
        if err != nil {
19✔
573
                return nil, fmt.Errorf("unable to fetch revocation state for "+
×
574
                        "chan_point=%v", chanState.FundingOutpoint)
×
575
        }
×
576

577
        return &chainSet{
19✔
578
                remoteStateNum:      remoteStateNum,
19✔
579
                commitSet:           commitSet,
19✔
580
                localCommit:         *localCommit,
19✔
581
                remoteCommit:        *remoteCommit,
19✔
582
                remotePendingCommit: remotePendingCommit,
19✔
583
        }, nil
19✔
584
}
585

586
// closeObserver is a dedicated goroutine that will watch for any closes of the
587
// channel that it's watching on chain. In the event of an on-chain event, the
588
// close observer will assembled the proper materials required to claim the
589
// funds of the channel on-chain (if required), then dispatch these as
590
// notifications to all subscribers.
591
func (c *chainWatcher) closeObserver(spendNtfn *chainntnfs.SpendEvent) {
30✔
592
        defer c.wg.Done()
30✔
593

30✔
594
        log.Infof("Close observer for ChannelPoint(%v) active",
30✔
595
                c.cfg.chanState.FundingOutpoint)
30✔
596

30✔
597
        // If this is a taproot channel, before we proceed, we want to ensure
30✔
598
        // that the expected funding output has confirmed on chain.
30✔
599
        if c.cfg.chanState.ChanType.IsTaproot() {
34✔
600
                fundingPoint := c.cfg.chanState.FundingOutpoint
4✔
601

4✔
602
                confNtfn, err := c.cfg.notifier.RegisterConfirmationsNtfn(
4✔
603
                        &fundingPoint.Hash, c.fundingPkScript, 1, c.heightHint,
4✔
604
                )
4✔
605
                if err != nil {
4✔
606
                        log.Warnf("unable to register for conf: %v", err)
×
607
                }
×
608

609
                log.Infof("Waiting for taproot ChannelPoint(%v) to confirm...",
4✔
610
                        c.cfg.chanState.FundingOutpoint)
4✔
611

4✔
612
                select {
4✔
613
                case _, ok := <-confNtfn.Confirmed:
4✔
614
                        // If the channel was closed, then this means that the
4✔
615
                        // notifier exited, so we will as well.
4✔
616
                        if !ok {
4✔
617
                                return
×
618
                        }
×
619
                case <-c.quit:
4✔
620
                        return
4✔
621
                }
622
        }
623

624
        select {
30✔
625
        // We've detected a spend of the channel onchain! Depending on the type
626
        // of spend, we'll act accordingly, so we'll examine the spending
627
        // transaction to determine what we should do.
628
        //
629
        // TODO(Roasbeef): need to be able to ensure this only triggers
630
        // on confirmation, to ensure if multiple txns are broadcast, we
631
        // act on the one that's timestamped
632
        case commitSpend, ok := <-spendNtfn.Spend:
19✔
633
                // If the channel was closed, then this means that the notifier
19✔
634
                // exited, so we will as well.
19✔
635
                if !ok {
19✔
636
                        return
×
637
                }
×
638

639
                // Otherwise, the remote party might have broadcast a prior
640
                // revoked state...!!!
641
                commitTxBroadcast := commitSpend.SpendingTx
19✔
642

19✔
643
                // First, we'll construct the chainset which includes all the
19✔
644
                // data we need to dispatch an event to our subscribers about
19✔
645
                // this possible channel close event.
19✔
646
                chainSet, err := newChainSet(c.cfg.chanState)
19✔
647
                if err != nil {
19✔
648
                        log.Errorf("unable to create commit set: %v", err)
×
649
                        return
×
650
                }
×
651

652
                // Decode the state hint encoded within the commitment
653
                // transaction to determine if this is a revoked state or not.
654
                obfuscator := c.stateHintObfuscator
19✔
655
                broadcastStateNum := c.cfg.extractStateNumHint(
19✔
656
                        commitTxBroadcast, obfuscator,
19✔
657
                )
19✔
658

19✔
659
                // We'll go on to check whether it could be our own commitment
19✔
660
                // that was published and know is confirmed.
19✔
661
                ok, err = c.handleKnownLocalState(
19✔
662
                        commitSpend, broadcastStateNum, chainSet,
19✔
663
                )
19✔
664
                if err != nil {
19✔
665
                        log.Errorf("Unable to handle known local state: %v",
×
666
                                err)
×
667
                        return
×
668
                }
×
669

670
                if ok {
25✔
671
                        return
6✔
672
                }
6✔
673

674
                // Now that we know it is neither a non-cooperative closure nor
675
                // a local close with the latest state, we check if it is the
676
                // remote that closed with any prior or current state.
677
                ok, err = c.handleKnownRemoteState(
17✔
678
                        commitSpend, broadcastStateNum, chainSet,
17✔
679
                )
17✔
680
                if err != nil {
17✔
681
                        log.Errorf("Unable to handle known remote state: %v",
×
682
                                err)
×
683
                        return
×
684
                }
×
685

686
                if ok {
23✔
687
                        return
6✔
688
                }
6✔
689

690
                // Next, we'll check to see if this is a cooperative channel
691
                // closure or not. This is characterized by having an input
692
                // sequence number that's finalized. This won't happen with
693
                // regular commitment transactions due to the state hint
694
                // encoding scheme.
695
                switch commitTxBroadcast.TxIn[0].Sequence {
15✔
696
                case wire.MaxTxInSequenceNum:
4✔
697
                        fallthrough
4✔
698
                case mempool.MaxRBFSequence:
4✔
699
                        // TODO(roasbeef): rare but possible, need itest case
4✔
700
                        // for
4✔
701
                        err := c.dispatchCooperativeClose(commitSpend)
4✔
702
                        if err != nil {
4✔
703
                                log.Errorf("unable to handle co op close: %v", err)
×
704
                        }
×
705
                        return
4✔
706
                }
707

708
                log.Warnf("Unknown commitment broadcast for "+
15✔
709
                        "ChannelPoint(%v) ", c.cfg.chanState.FundingOutpoint)
15✔
710

15✔
711
                // We'll try to recover as best as possible from losing state.
15✔
712
                // We first check if this was a local unknown state. This could
15✔
713
                // happen if we force close, then lose state or attempt
15✔
714
                // recovery before the commitment confirms.
15✔
715
                ok, err = c.handleUnknownLocalState(
15✔
716
                        commitSpend, broadcastStateNum, chainSet,
15✔
717
                )
15✔
718
                if err != nil {
15✔
719
                        log.Errorf("Unable to handle known local state: %v",
×
720
                                err)
×
721
                        return
×
722
                }
×
723

724
                if ok {
26✔
725
                        return
11✔
726
                }
11✔
727

728
                // Since it was neither a known remote state, nor a local state
729
                // that was published, it most likely mean we lost state and
730
                // the remote node closed. In this case we must start the DLP
731
                // protocol in hope of getting our money back.
732
                ok, err = c.handleUnknownRemoteState(
8✔
733
                        commitSpend, broadcastStateNum, chainSet,
8✔
734
                )
8✔
735
                if err != nil {
8✔
736
                        log.Errorf("Unable to handle unknown remote state: %v",
×
737
                                err)
×
738
                        return
×
739
                }
×
740

741
                if ok {
16✔
742
                        return
8✔
743
                }
8✔
744

745
                log.Warnf("Unable to handle spending tx %v of channel point %v",
×
746
                        commitTxBroadcast.TxHash(), c.cfg.chanState.FundingOutpoint)
×
747
                return
×
748

749
        // The chainWatcher has been signalled to exit, so we'll do so now.
750
        case <-c.quit:
15✔
751
                return
15✔
752
        }
753
}
754

755
// handleKnownLocalState checks whether the passed spend is a local state that
756
// is known to us (the current state). If so we will act on this state using
757
// the passed chainSet. If this is not a known local state, false is returned.
758
func (c *chainWatcher) handleKnownLocalState(
759
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
760
        chainSet *chainSet) (bool, error) {
19✔
761

19✔
762
        // If the channel is recovered, we won't have a local commit to check
19✔
763
        // against, so immediately return.
19✔
764
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
23✔
765
                return false, nil
4✔
766
        }
4✔
767

768
        commitTxBroadcast := commitSpend.SpendingTx
19✔
769
        commitHash := commitTxBroadcast.TxHash()
19✔
770

19✔
771
        // Check whether our latest local state hit the chain.
19✔
772
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
36✔
773
                return false, nil
17✔
774
        }
17✔
775

776
        chainSet.commitSet.ConfCommitKey = &LocalHtlcSet
6✔
777
        if err := c.dispatchLocalForceClose(
6✔
778
                commitSpend, broadcastStateNum, chainSet.commitSet,
6✔
779
        ); err != nil {
6✔
780
                return false, fmt.Errorf("unable to handle local"+
×
781
                        "close for chan_point=%v: %v",
×
782
                        c.cfg.chanState.FundingOutpoint, err)
×
783
        }
×
784

785
        return true, nil
6✔
786
}
787

788
// handleKnownRemoteState checks whether the passed spend is a remote state
789
// that is known to us (a revoked, current or pending state). If so we will act
790
// on this state using the passed chainSet. If this is not a known remote
791
// state, false is returned.
792
func (c *chainWatcher) handleKnownRemoteState(
793
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
794
        chainSet *chainSet) (bool, error) {
17✔
795

17✔
796
        // If the channel is recovered, we won't have any remote commit to
17✔
797
        // check against, so imemdiately return.
17✔
798
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
21✔
799
                return false, nil
4✔
800
        }
4✔
801

802
        commitTxBroadcast := commitSpend.SpendingTx
17✔
803
        commitHash := commitTxBroadcast.TxHash()
17✔
804

17✔
805
        switch {
17✔
806
        // If the spending transaction matches the current latest state, then
807
        // they've initiated a unilateral close. So we'll trigger the
808
        // unilateral close signal so subscribers can clean up the state as
809
        // necessary.
810
        case chainSet.remoteCommit.CommitTx.TxHash() == commitHash:
5✔
811
                log.Infof("Remote party broadcast base set, "+
5✔
812
                        "commit_num=%v", chainSet.remoteStateNum)
5✔
813

5✔
814
                chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
5✔
815
                err := c.dispatchRemoteForceClose(
5✔
816
                        commitSpend, chainSet.remoteCommit,
5✔
817
                        chainSet.commitSet,
5✔
818
                        c.cfg.chanState.RemoteCurrentRevocation,
5✔
819
                )
5✔
820
                if err != nil {
5✔
821
                        return false, fmt.Errorf("unable to handle remote "+
×
822
                                "close for chan_point=%v: %v",
×
823
                                c.cfg.chanState.FundingOutpoint, err)
×
824
                }
×
825

826
                return true, nil
5✔
827

828
        // We'll also handle the case of the remote party broadcasting
829
        // their commitment transaction which is one height above ours.
830
        // This case can arise when we initiate a state transition, but
831
        // the remote party has a fail crash _after_ accepting the new
832
        // state, but _before_ sending their signature to us.
833
        case chainSet.remotePendingCommit != nil &&
834
                chainSet.remotePendingCommit.CommitTx.TxHash() == commitHash:
1✔
835

1✔
836
                log.Infof("Remote party broadcast pending set, "+
1✔
837
                        "commit_num=%v", chainSet.remoteStateNum+1)
1✔
838

1✔
839
                chainSet.commitSet.ConfCommitKey = &RemotePendingHtlcSet
1✔
840
                err := c.dispatchRemoteForceClose(
1✔
841
                        commitSpend, *chainSet.remotePendingCommit,
1✔
842
                        chainSet.commitSet,
1✔
843
                        c.cfg.chanState.RemoteNextRevocation,
1✔
844
                )
1✔
845
                if err != nil {
1✔
846
                        return false, fmt.Errorf("unable to handle remote "+
×
847
                                "close for chan_point=%v: %v",
×
848
                                c.cfg.chanState.FundingOutpoint, err)
×
849
                }
×
850

851
                return true, nil
1✔
852
        }
853

854
        // This is neither a remote force close or a "future" commitment, we
855
        // now check whether it's a remote breach and properly handle it.
856
        return c.handlePossibleBreach(commitSpend, broadcastStateNum, chainSet)
15✔
857
}
858

859
// handlePossibleBreach checks whether the remote has breached and dispatches a
860
// breach resolution to claim funds.
861
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
862
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
15✔
863

15✔
864
        // We check if we have a revoked state at this state num that matches
15✔
865
        // the spend transaction.
15✔
866
        spendHeight := uint32(commitSpend.SpendingHeight)
15✔
867
        retribution, err := lnwallet.NewBreachRetribution(
15✔
868
                c.cfg.chanState, broadcastStateNum, spendHeight,
15✔
869
                commitSpend.SpendingTx,
15✔
870
        )
15✔
871

15✔
872
        switch {
15✔
873
        // If we had no log entry at this height, this was not a revoked state.
874
        case err == channeldb.ErrLogEntryNotFound:
12✔
875
                return false, nil
12✔
876
        case err == channeldb.ErrNoPastDeltas:
7✔
877
                return false, nil
7✔
878

879
        case err != nil:
×
880
                return false, fmt.Errorf("unable to create breach "+
×
881
                        "retribution: %v", err)
×
882
        }
883

884
        // We found a revoked state at this height, but it could still be our
885
        // own broadcasted state we are looking at. Therefore check that the
886
        // commit matches before assuming it was a breach.
887
        commitHash := commitSpend.SpendingTx.TxHash()
4✔
888
        if retribution.BreachTxHash != commitHash {
4✔
889
                return false, nil
×
890
        }
×
891

892
        // Create an AnchorResolution for the breached state.
893
        anchorRes, err := lnwallet.NewAnchorResolution(
4✔
894
                c.cfg.chanState, commitSpend.SpendingTx, retribution.KeyRing,
4✔
895
                lntypes.Remote,
4✔
896
        )
4✔
897
        if err != nil {
4✔
898
                return false, fmt.Errorf("unable to create anchor "+
×
899
                        "resolution: %v", err)
×
900
        }
×
901

902
        // We'll set the ConfCommitKey here as the remote htlc set. This is
903
        // only used to ensure a nil-pointer-dereference doesn't occur and is
904
        // not used otherwise. The HTLC's may not exist for the
905
        // RemotePendingHtlcSet.
906
        chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
4✔
907

4✔
908
        // THEY'RE ATTEMPTING TO VIOLATE THE CONTRACT LAID OUT WITHIN THE
4✔
909
        // PAYMENT CHANNEL. Therefore we close the signal indicating a revoked
4✔
910
        // broadcast to allow subscribers to swiftly dispatch justice!!!
4✔
911
        err = c.dispatchContractBreach(
4✔
912
                commitSpend, chainSet, broadcastStateNum, retribution,
4✔
913
                anchorRes,
4✔
914
        )
4✔
915
        if err != nil {
4✔
916
                return false, fmt.Errorf("unable to handle channel "+
×
917
                        "breach for chan_point=%v: %v",
×
918
                        c.cfg.chanState.FundingOutpoint, err)
×
919
        }
×
920

921
        return true, nil
4✔
922
}
923

924
// handleUnknownRemoteState is the last attempt we make at reclaiming funds
925
// from the closed channel, by checkin whether the passed spend _could_ be a
926
// remote spend that is unknown to us (we lost state). We will try to initiate
927
// Data Loss Protection in order to restore our commit point and reclaim our
928
// funds from the channel. If we are not able to act on it, false is returned.
929
func (c *chainWatcher) handleUnknownRemoteState(
930
        commitSpend *chainntnfs.SpendDetail, broadcastStateNum uint64,
931
        chainSet *chainSet) (bool, error) {
8✔
932

8✔
933
        log.Warnf("Remote node broadcast state #%v, "+
8✔
934
                "which is more than 1 beyond best known "+
8✔
935
                "state #%v!!! Attempting recovery...",
8✔
936
                broadcastStateNum, chainSet.remoteStateNum)
8✔
937

8✔
938
        // If this isn't a tweakless commitment, then we'll need to wait for
8✔
939
        // the remote party's latest unrevoked commitment point to be presented
8✔
940
        // to us as we need this to sweep. Otherwise, we can dispatch the
8✔
941
        // remote close and sweep immediately using a fake commitPoint as it
8✔
942
        // isn't actually needed for recovery anymore.
8✔
943
        commitPoint := c.cfg.chanState.RemoteCurrentRevocation
8✔
944
        tweaklessCommit := c.cfg.chanState.ChanType.IsTweakless()
8✔
945
        if !tweaklessCommit {
12✔
946
                commitPoint = c.waitForCommitmentPoint()
4✔
947
                if commitPoint == nil {
4✔
948
                        return false, fmt.Errorf("unable to get commit point")
×
949
                }
×
950

951
                log.Infof("Recovered commit point(%x) for "+
4✔
952
                        "channel(%v)! Now attempting to use it to "+
4✔
953
                        "sweep our funds...",
4✔
954
                        commitPoint.SerializeCompressed(),
4✔
955
                        c.cfg.chanState.FundingOutpoint)
4✔
956
        } else {
4✔
957
                log.Infof("ChannelPoint(%v) is tweakless, "+
4✔
958
                        "moving to sweep directly on chain",
4✔
959
                        c.cfg.chanState.FundingOutpoint)
4✔
960
        }
4✔
961

962
        // Since we don't have the commitment stored for this state, we'll just
963
        // pass an empty commitment within the commitment set. Note that this
964
        // means we won't be able to recover any HTLC funds.
965
        //
966
        // TODO(halseth): can we try to recover some HTLCs?
967
        chainSet.commitSet.ConfCommitKey = &RemoteHtlcSet
8✔
968
        err := c.dispatchRemoteForceClose(
8✔
969
                commitSpend, channeldb.ChannelCommitment{},
8✔
970
                chainSet.commitSet, commitPoint,
8✔
971
        )
8✔
972
        if err != nil {
8✔
973
                return false, fmt.Errorf("unable to handle remote "+
×
974
                        "close for chan_point=%v: %v",
×
975
                        c.cfg.chanState.FundingOutpoint, err)
×
976
        }
×
977

978
        return true, nil
8✔
979
}
980

981
// toSelfAmount takes a transaction and returns the sum of all outputs that pay
982
// to a script that the wallet controls or the channel defines as its delivery
983
// script . If no outputs pay to us (determined by these criteria), then we
984
// return zero. This is possible as our output may have been trimmed due to
985
// being dust.
986
func (c *chainWatcher) toSelfAmount(tx *wire.MsgTx) btcutil.Amount {
4✔
987
        // There are two main cases we have to handle here. First, in the coop
4✔
988
        // close case we will always have saved the delivery address we used
4✔
989
        // whether it was from the upfront shutdown, from the delivery address
4✔
990
        // requested at close time, or even an automatically generated one. All
4✔
991
        // coop-close cases can be identified in the following manner:
4✔
992
        shutdown, _ := c.cfg.chanState.ShutdownInfo()
4✔
993
        oDeliveryAddr := fn.MapOption(
4✔
994
                func(i channeldb.ShutdownInfo) lnwire.DeliveryAddress {
8✔
995
                        return i.DeliveryScript.Val
4✔
996
                })(shutdown)
4✔
997

998
        // Here we define a function capable of identifying whether an output
999
        // corresponds with our local delivery script from a ShutdownInfo if we
1000
        // have a ShutdownInfo for this chainWatcher's underlying channel.
1001
        //
1002
        // isDeliveryOutput :: *TxOut -> bool
1003
        isDeliveryOutput := func(o *wire.TxOut) bool {
8✔
1004
                return fn.ElimOption(
4✔
1005
                        oDeliveryAddr,
4✔
1006
                        // If we don't have a delivery addr, then the output
4✔
1007
                        // can't match it.
4✔
1008
                        func() bool { return false },
4✔
1009
                        // Otherwise if the PkScript of the TxOut matches our
1010
                        // delivery script then this is a delivery output.
1011
                        func(a lnwire.DeliveryAddress) bool {
4✔
1012
                                return slices.Equal(a, o.PkScript)
4✔
1013
                        },
4✔
1014
                )
1015
        }
1016

1017
        // Here we define a function capable of identifying whether an output
1018
        // belongs to the LND wallet. We use this as a heuristic in the case
1019
        // where we might be looking for spendable force closure outputs.
1020
        //
1021
        // isWalletOutput :: *TxOut -> bool
1022
        isWalletOutput := func(out *wire.TxOut) bool {
8✔
1023
                _, addrs, _, err := txscript.ExtractPkScriptAddrs(
4✔
1024
                        // Doesn't matter what net we actually pass in.
4✔
1025
                        out.PkScript, &chaincfg.TestNet3Params,
4✔
1026
                )
4✔
1027
                if err != nil {
4✔
1028
                        return false
×
1029
                }
×
1030

1031
                return fn.Any(c.cfg.isOurAddr, addrs)
4✔
1032
        }
1033

1034
        // Grab all of the outputs that correspond with our delivery address
1035
        // or our wallet is aware of.
1036
        outs := fn.Filter(fn.PredOr(isDeliveryOutput, isWalletOutput), tx.TxOut)
4✔
1037

4✔
1038
        // Grab the values for those outputs.
4✔
1039
        vals := fn.Map(func(o *wire.TxOut) int64 { return o.Value }, outs)
8✔
1040

1041
        // Return the sum.
1042
        return btcutil.Amount(fn.Sum(vals))
4✔
1043
}
1044

1045
// dispatchCooperativeClose processed a detect cooperative channel closure.
1046
// We'll use the spending transaction to locate our output within the
1047
// transaction, then clean up the database state. We'll also dispatch a
1048
// notification to all subscribers that the channel has been closed in this
1049
// manner.
1050
func (c *chainWatcher) dispatchCooperativeClose(commitSpend *chainntnfs.SpendDetail) error {
4✔
1051
        broadcastTx := commitSpend.SpendingTx
4✔
1052

4✔
1053
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
4✔
1054
                c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
4✔
1055

4✔
1056
        // If the input *is* final, then we'll check to see which output is
4✔
1057
        // ours.
4✔
1058
        localAmt := c.toSelfAmount(broadcastTx)
4✔
1059

4✔
1060
        // Once this is known, we'll mark the state as fully closed in the
4✔
1061
        // database. We can do this as a cooperatively closed channel has all
4✔
1062
        // its outputs resolved after only one confirmation.
4✔
1063
        closeSummary := &channeldb.ChannelCloseSummary{
4✔
1064
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
4✔
1065
                ChainHash:               c.cfg.chanState.ChainHash,
4✔
1066
                ClosingTXID:             *commitSpend.SpenderTxHash,
4✔
1067
                RemotePub:               c.cfg.chanState.IdentityPub,
4✔
1068
                Capacity:                c.cfg.chanState.Capacity,
4✔
1069
                CloseHeight:             uint32(commitSpend.SpendingHeight),
4✔
1070
                SettledBalance:          localAmt,
4✔
1071
                CloseType:               channeldb.CooperativeClose,
4✔
1072
                ShortChanID:             c.cfg.chanState.ShortChanID(),
4✔
1073
                IsPending:               true,
4✔
1074
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
4✔
1075
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
4✔
1076
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
4✔
1077
        }
4✔
1078

4✔
1079
        // Attempt to add a channel sync message to the close summary.
4✔
1080
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
4✔
1081
        if err != nil {
4✔
1082
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1083
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1084
        } else {
4✔
1085
                closeSummary.LastChanSyncMsg = chanSync
4✔
1086
        }
4✔
1087

1088
        // Create a summary of all the information needed to handle the
1089
        // cooperative closure.
1090
        closeInfo := &CooperativeCloseInfo{
4✔
1091
                ChannelCloseSummary: closeSummary,
4✔
1092
        }
4✔
1093

4✔
1094
        // With the event processed, we'll now notify all subscribers of the
4✔
1095
        // event.
4✔
1096
        c.Lock()
4✔
1097
        for _, sub := range c.clientSubscriptions {
8✔
1098
                select {
4✔
1099
                case sub.CooperativeClosure <- closeInfo:
4✔
1100
                case <-c.quit:
×
1101
                        c.Unlock()
×
1102
                        return fmt.Errorf("exiting")
×
1103
                }
1104
        }
1105
        c.Unlock()
4✔
1106

4✔
1107
        return nil
4✔
1108
}
1109

1110
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
1111
func (c *chainWatcher) dispatchLocalForceClose(
1112
        commitSpend *chainntnfs.SpendDetail,
1113
        stateNum uint64, commitSet CommitSet) error {
13✔
1114

13✔
1115
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
13✔
1116
                "detected", c.cfg.chanState.FundingOutpoint)
13✔
1117

13✔
1118
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
13✔
1119
                c.cfg.chanState, c.cfg.signer,
13✔
1120
                commitSpend.SpendingTx, stateNum,
13✔
1121
        )
13✔
1122
        if err != nil {
13✔
1123
                return err
×
1124
        }
×
1125

1126
        // As we've detected that the channel has been closed, immediately
1127
        // creating a close summary for future usage by related sub-systems.
1128
        chanSnapshot := forceClose.ChanSnapshot
13✔
1129
        closeSummary := &channeldb.ChannelCloseSummary{
13✔
1130
                ChanPoint:               chanSnapshot.ChannelPoint,
13✔
1131
                ChainHash:               chanSnapshot.ChainHash,
13✔
1132
                ClosingTXID:             forceClose.CloseTx.TxHash(),
13✔
1133
                RemotePub:               &chanSnapshot.RemoteIdentity,
13✔
1134
                Capacity:                chanSnapshot.Capacity,
13✔
1135
                CloseType:               channeldb.LocalForceClose,
13✔
1136
                IsPending:               true,
13✔
1137
                ShortChanID:             c.cfg.chanState.ShortChanID(),
13✔
1138
                CloseHeight:             uint32(commitSpend.SpendingHeight),
13✔
1139
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
13✔
1140
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
13✔
1141
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
13✔
1142
        }
13✔
1143

13✔
1144
        // If our commitment output isn't dust or we have active HTLC's on the
13✔
1145
        // commitment transaction, then we'll populate the balances on the
13✔
1146
        // close channel summary.
13✔
1147
        if forceClose.CommitResolution != nil {
23✔
1148
                closeSummary.SettledBalance = chanSnapshot.LocalBalance.ToSatoshis()
10✔
1149
                closeSummary.TimeLockedBalance = chanSnapshot.LocalBalance.ToSatoshis()
10✔
1150
        }
10✔
1151
        for _, htlc := range forceClose.HtlcResolutions.OutgoingHTLCs {
17✔
1152
                htlcValue := btcutil.Amount(htlc.SweepSignDesc.Output.Value)
4✔
1153
                closeSummary.TimeLockedBalance += htlcValue
4✔
1154
        }
4✔
1155

1156
        // Attempt to add a channel sync message to the close summary.
1157
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
13✔
1158
        if err != nil {
13✔
1159
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1160
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1161
        } else {
13✔
1162
                closeSummary.LastChanSyncMsg = chanSync
13✔
1163
        }
13✔
1164

1165
        // With the event processed, we'll now notify all subscribers of the
1166
        // event.
1167
        closeInfo := &LocalUnilateralCloseInfo{
13✔
1168
                SpendDetail:            commitSpend,
13✔
1169
                LocalForceCloseSummary: forceClose,
13✔
1170
                ChannelCloseSummary:    closeSummary,
13✔
1171
                CommitSet:              commitSet,
13✔
1172
        }
13✔
1173
        c.Lock()
13✔
1174
        for _, sub := range c.clientSubscriptions {
26✔
1175
                select {
13✔
1176
                case sub.LocalUnilateralClosure <- closeInfo:
13✔
1177
                case <-c.quit:
×
1178
                        c.Unlock()
×
1179
                        return fmt.Errorf("exiting")
×
1180
                }
1181
        }
1182
        c.Unlock()
13✔
1183

13✔
1184
        return nil
13✔
1185
}
1186

1187
// dispatchRemoteForceClose processes a detected unilateral channel closure by
1188
// the remote party. This function will prepare a UnilateralCloseSummary which
1189
// will then be sent to any subscribers allowing them to resolve all our funds
1190
// in the channel on chain. Once this close summary is prepared, all registered
1191
// subscribers will receive a notification of this event. The commitPoint
1192
// argument should be set to the per_commitment_point corresponding to the
1193
// spending commitment.
1194
//
1195
// NOTE: The remoteCommit argument should be set to the stored commitment for
1196
// this particular state. If we don't have the commitment stored (should only
1197
// happen in case we have lost state) it should be set to an empty struct, in
1198
// which case we will attempt to sweep the non-HTLC output using the passed
1199
// commitPoint.
1200
func (c *chainWatcher) dispatchRemoteForceClose(
1201
        commitSpend *chainntnfs.SpendDetail,
1202
        remoteCommit channeldb.ChannelCommitment,
1203
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
10✔
1204

10✔
1205
        log.Infof("Unilateral close of ChannelPoint(%v) "+
10✔
1206
                "detected", c.cfg.chanState.FundingOutpoint)
10✔
1207

10✔
1208
        // First, we'll create a closure summary that contains all the
10✔
1209
        // materials required to let each subscriber sweep the funds in the
10✔
1210
        // channel on-chain.
10✔
1211
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
10✔
1212
                c.cfg.chanState, c.cfg.signer, commitSpend,
10✔
1213
                remoteCommit, commitPoint,
10✔
1214
        )
10✔
1215
        if err != nil {
10✔
1216
                return err
×
1217
        }
×
1218

1219
        // With the event processed, we'll now notify all subscribers of the
1220
        // event.
1221
        c.Lock()
10✔
1222
        for _, sub := range c.clientSubscriptions {
20✔
1223
                select {
10✔
1224
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
1225
                        UnilateralCloseSummary: uniClose,
1226
                        CommitSet:              commitSet,
1227
                }:
10✔
1228
                case <-c.quit:
×
1229
                        c.Unlock()
×
1230
                        return fmt.Errorf("exiting")
×
1231
                }
1232
        }
1233
        c.Unlock()
10✔
1234

10✔
1235
        return nil
10✔
1236
}
1237

1238
// dispatchContractBreach processes a detected contract breached by the remote
1239
// party. This method is to be called once we detect that the remote party has
1240
// broadcast a prior revoked commitment state. This method well prepare all the
1241
// materials required to bring the cheater to justice, then notify all
1242
// registered subscribers of this event.
1243
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
1244
        chainSet *chainSet, broadcastStateNum uint64,
1245
        retribution *lnwallet.BreachRetribution,
1246
        anchorRes *lnwallet.AnchorResolution) error {
4✔
1247

4✔
1248
        log.Warnf("Remote peer has breached the channel contract for "+
4✔
1249
                "ChannelPoint(%v). Revoked state #%v was broadcast!!!",
4✔
1250
                c.cfg.chanState.FundingOutpoint, broadcastStateNum)
4✔
1251

4✔
1252
        if err := c.cfg.chanState.MarkBorked(); err != nil {
4✔
1253
                return fmt.Errorf("unable to mark channel as borked: %w", err)
×
1254
        }
×
1255

1256
        spendHeight := uint32(spendEvent.SpendingHeight)
4✔
1257

4✔
1258
        log.Debugf("Punishment breach retribution created: %v",
4✔
1259
                lnutils.NewLogClosure(func() string {
8✔
1260
                        retribution.KeyRing.LocalHtlcKey = nil
4✔
1261
                        retribution.KeyRing.RemoteHtlcKey = nil
4✔
1262
                        retribution.KeyRing.ToLocalKey = nil
4✔
1263
                        retribution.KeyRing.ToRemoteKey = nil
4✔
1264
                        retribution.KeyRing.RevocationKey = nil
4✔
1265
                        return spew.Sdump(retribution)
4✔
1266
                }))
4✔
1267

1268
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
4✔
1269
        closeSummary := channeldb.ChannelCloseSummary{
4✔
1270
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
4✔
1271
                ChainHash:               c.cfg.chanState.ChainHash,
4✔
1272
                ClosingTXID:             *spendEvent.SpenderTxHash,
4✔
1273
                CloseHeight:             spendHeight,
4✔
1274
                RemotePub:               c.cfg.chanState.IdentityPub,
4✔
1275
                Capacity:                c.cfg.chanState.Capacity,
4✔
1276
                SettledBalance:          settledBalance,
4✔
1277
                CloseType:               channeldb.BreachClose,
4✔
1278
                IsPending:               true,
4✔
1279
                ShortChanID:             c.cfg.chanState.ShortChanID(),
4✔
1280
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
4✔
1281
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
4✔
1282
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
4✔
1283
        }
4✔
1284

4✔
1285
        // Attempt to add a channel sync message to the close summary.
4✔
1286
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
4✔
1287
        if err != nil {
4✔
1288
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1289
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1290
        } else {
4✔
1291
                closeSummary.LastChanSyncMsg = chanSync
4✔
1292
        }
4✔
1293

1294
        // Hand the retribution info over to the BreachArbitrator. This function
1295
        // will wait for a response from the breach arbiter and then proceed to
1296
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1297
        // will then mark the channel as closed after resolutions and the
1298
        // commit set are logged in the arbitrator log.
1299
        if err := c.cfg.contractBreach(retribution); err != nil {
4✔
1300
                log.Errorf("unable to hand breached contract off to "+
×
1301
                        "BreachArbitrator: %v", err)
×
1302
                return err
×
1303
        }
×
1304

1305
        breachRes := &BreachResolution{
4✔
1306
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
4✔
1307
        }
4✔
1308

4✔
1309
        breachInfo := &BreachCloseInfo{
4✔
1310
                CommitHash:       spendEvent.SpendingTx.TxHash(),
4✔
1311
                BreachResolution: breachRes,
4✔
1312
                AnchorResolution: anchorRes,
4✔
1313
                CommitSet:        chainSet.commitSet,
4✔
1314
                CloseSummary:     closeSummary,
4✔
1315
        }
4✔
1316

4✔
1317
        // With the event processed and channel closed, we'll now notify all
4✔
1318
        // subscribers of the event.
4✔
1319
        c.Lock()
4✔
1320
        for _, sub := range c.clientSubscriptions {
8✔
1321
                select {
4✔
1322
                case sub.ContractBreach <- breachInfo:
4✔
1323
                case <-c.quit:
×
1324
                        c.Unlock()
×
1325
                        return fmt.Errorf("quitting")
×
1326
                }
1327
        }
1328
        c.Unlock()
4✔
1329

4✔
1330
        return nil
4✔
1331
}
1332

1333
// waitForCommitmentPoint waits for the commitment point to be inserted into
1334
// the local database. We'll use this method in the DLP case, to wait for the
1335
// remote party to send us their point, as we can't proceed until we have that.
1336
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
4✔
1337
        // If we are lucky, the remote peer sent us the correct commitment
4✔
1338
        // point during channel sync, such that we can sweep our funds. If we
4✔
1339
        // cannot find the commit point, there's not much we can do other than
4✔
1340
        // wait for us to retrieve it. We will attempt to retrieve it from the
4✔
1341
        // peer each time we connect to it.
4✔
1342
        //
4✔
1343
        // TODO(halseth): actively initiate re-connection to the peer?
4✔
1344
        backoff := minCommitPointPollTimeout
4✔
1345
        for {
8✔
1346
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
4✔
1347
                if err == nil {
8✔
1348
                        return commitPoint
4✔
1349
                }
4✔
1350

1351
                log.Errorf("Unable to retrieve commitment point for "+
×
1352
                        "channel(%v) with lost state: %v. Retrying in %v.",
×
1353
                        c.cfg.chanState.FundingOutpoint, err, backoff)
×
1354

×
1355
                select {
×
1356
                // Wait before retrying, with an exponential backoff.
1357
                case <-time.After(backoff):
×
1358
                        backoff = 2 * backoff
×
1359
                        if backoff > maxCommitPointPollTimeout {
×
1360
                                backoff = maxCommitPointPollTimeout
×
1361
                        }
×
1362

1363
                case <-c.quit:
×
1364
                        return nil
×
1365
                }
1366
        }
1367
}
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