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

lightningnetwork / lnd / 12115442155

02 Dec 2024 08:28AM UTC coverage: 48.662% (-10.3%) from 58.948%
12115442155

Pull #9175

github

ellemouton
netann: update ChanAnn2 validation to work for P2WSH channels

This commit expands the ChannelAnnouncement2 validation for the case
where it is announcing a P2WSH channel.
Pull Request #9175: lnwire+netann: update structure of g175 messages to be pure TLV

6 of 314 new or added lines in 9 files covered. (1.91%)

27532 existing lines in 434 files now uncovered.

97890 of 201164 relevant lines covered (48.66%)

0.52 hits per line

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

78.12
/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
        // When the ConfCommitKey is set, it signals that the commitment tx was
97
        // confirmed in the chain.
98
        ConfCommitKey fn.Option[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 {
1✔
108
        if c == nil {
1✔
109
                return true
×
110
        }
×
111

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

118
        return true
1✔
119
}
120

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

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

130
        return htlcSets
1✔
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
        // auxResolver is used to supplement contract resolution.
201
        auxResolver fn.Option[lnwallet.AuxContractResolver]
202
}
203

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

213
        quit chan struct{}
214
        wg   sync.WaitGroup
215

216
        cfg chainWatcherConfig
217

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

222
        // fundingPkScript is the pkScript of the funding output.
223
        fundingPkScript []byte
224

225
        // heightHint is the height hint used to checkpoint scans on chain for
226
        // conf/spend events.
227
        heightHint uint32
228

229
        // All the fields below are protected by this mutex.
230
        sync.Mutex
231

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

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

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

263
        return &chainWatcher{
1✔
264
                cfg:                 cfg,
1✔
265
                stateHintObfuscator: stateHint,
1✔
266
                quit:                make(chan struct{}),
1✔
267
                clientSubscriptions: make(map[uint64]*ChainEventSubscription),
1✔
268
        }, nil
1✔
269
}
270

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

278
        chanState := c.cfg.chanState
1✔
279
        log.Debugf("Starting chain watcher for ChannelPoint(%v)",
1✔
280
                chanState.FundingOutpoint)
1✔
281

1✔
282
        // First, we'll register for a notification to be dispatched if the
1✔
283
        // funding output is spent.
1✔
284
        fundingOut := &chanState.FundingOutpoint
1✔
285

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

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

309
        localKey := chanState.LocalChanCfg.MultiSigKey.PubKey
1✔
310
        remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
1✔
311

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

336
        spendNtfn, err := c.cfg.notifier.RegisterSpendNtfn(
1✔
337
                fundingOut, c.fundingPkScript, c.heightHint,
1✔
338
        )
1✔
339
        if err != nil {
1✔
340
                return err
×
341
        }
×
342

343
        // With the spend notification obtained, we'll now dispatch the
344
        // closeObserver which will properly react to any changes.
345
        c.wg.Add(1)
1✔
346
        go c.closeObserver(spendNtfn)
1✔
347

1✔
348
        return nil
1✔
349
}
350

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

357
        close(c.quit)
1✔
358

1✔
359
        c.wg.Wait()
1✔
360

1✔
361
        return nil
1✔
362
}
363

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

1✔
370
        c.Lock()
1✔
371
        clientID := c.clientID
1✔
372
        c.clientID++
1✔
373
        c.Unlock()
1✔
374

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

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

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

1✔
395
        return sub
1✔
396
}
397

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

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

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

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

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

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

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

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

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

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

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

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

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

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

522
        return true, nil
1✔
523
}
524

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
631
        // If this is a taproot channel, before we proceed, we want to ensure
1✔
632
        // that the expected funding output has confirmed on chain.
1✔
633
        if c.cfg.chanState.ChanType.IsTaproot() {
2✔
634
                fundingPoint := c.cfg.chanState.FundingOutpoint
1✔
635

1✔
636
                confNtfn, err := c.cfg.notifier.RegisterConfirmationsNtfn(
1✔
637
                        &fundingPoint.Hash, c.fundingPkScript, 1, c.heightHint,
1✔
638
                )
1✔
639
                if err != nil {
1✔
640
                        log.Warnf("unable to register for conf: %v", err)
×
641
                }
×
642

643
                log.Infof("Waiting for taproot ChannelPoint(%v) to confirm...",
1✔
644
                        c.cfg.chanState.FundingOutpoint)
1✔
645

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

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

673
                // Otherwise, the remote party might have broadcast a prior
674
                // revoked state...!!!
675
                commitTxBroadcast := commitSpend.SpendingTx
1✔
676

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

686
                // Decode the state hint encoded within the commitment
687
                // transaction to determine if this is a revoked state or not.
688
                obfuscator := c.stateHintObfuscator
1✔
689
                broadcastStateNum := c.cfg.extractStateNumHint(
1✔
690
                        commitTxBroadcast, obfuscator,
1✔
691
                )
1✔
692

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

704
                if ok {
2✔
705
                        return
1✔
706
                }
1✔
707

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

720
                if ok {
2✔
721
                        return
1✔
722
                }
1✔
723

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

742
                log.Warnf("Unknown commitment broadcast for "+
1✔
743
                        "ChannelPoint(%v) ", c.cfg.chanState.FundingOutpoint)
1✔
744

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

758
                if ok {
2✔
759
                        return
1✔
760
                }
1✔
761

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

775
                if ok {
2✔
776
                        return
1✔
777
                }
1✔
778

779
                log.Warnf("Unable to handle spending tx %v of channel point %v",
×
780
                        commitTxBroadcast.TxHash(), c.cfg.chanState.FundingOutpoint)
×
781
                return
×
782

783
        // The chainWatcher has been signalled to exit, so we'll do so now.
784
        case <-c.quit:
1✔
785
                return
1✔
786
        }
787
}
788

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

1✔
796
        // If the channel is recovered, we won't have a local commit to check
1✔
797
        // against, so immediately return.
1✔
798
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
2✔
799
                return false, nil
1✔
800
        }
1✔
801

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

1✔
805
        // Check whether our latest local state hit the chain.
1✔
806
        if chainSet.localCommit.CommitTx.TxHash() != commitHash {
2✔
807
                return false, nil
1✔
808
        }
1✔
809

810
        chainSet.commitSet.ConfCommitKey = fn.Some(LocalHtlcSet)
1✔
811
        if err := c.dispatchLocalForceClose(
1✔
812
                commitSpend, broadcastStateNum, chainSet.commitSet,
1✔
813
        ); err != nil {
1✔
814
                return false, fmt.Errorf("unable to handle local"+
×
815
                        "close for chan_point=%v: %v",
×
816
                        c.cfg.chanState.FundingOutpoint, err)
×
817
        }
×
818

819
        return true, nil
1✔
820
}
821

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

1✔
830
        // If the channel is recovered, we won't have any remote commit to
1✔
831
        // check against, so imemdiately return.
1✔
832
        if c.cfg.chanState.HasChanStatus(channeldb.ChanStatusRestored) {
2✔
833
                return false, nil
1✔
834
        }
1✔
835

836
        commitTxBroadcast := commitSpend.SpendingTx
1✔
837
        commitHash := commitTxBroadcast.TxHash()
1✔
838

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

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

860
                return true, nil
1✔
861

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

×
UNCOV
870
                log.Infof("Remote party broadcast pending set, "+
×
UNCOV
871
                        "commit_num=%v", chainSet.remoteStateNum+1)
×
UNCOV
872

×
UNCOV
873
                chainSet.commitSet.ConfCommitKey = fn.Some(RemotePendingHtlcSet)
×
UNCOV
874
                err := c.dispatchRemoteForceClose(
×
UNCOV
875
                        commitSpend, *chainSet.remotePendingCommit,
×
UNCOV
876
                        chainSet.commitSet,
×
UNCOV
877
                        c.cfg.chanState.RemoteNextRevocation,
×
UNCOV
878
                )
×
UNCOV
879
                if err != nil {
×
880
                        return false, fmt.Errorf("unable to handle remote "+
×
881
                                "close for chan_point=%v: %v",
×
882
                                c.cfg.chanState.FundingOutpoint, err)
×
883
                }
×
884

UNCOV
885
                return true, nil
×
886
        }
887

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

893
// handlePossibleBreach checks whether the remote has breached and dispatches a
894
// breach resolution to claim funds.
895
func (c *chainWatcher) handlePossibleBreach(commitSpend *chainntnfs.SpendDetail,
896
        broadcastStateNum uint64, chainSet *chainSet) (bool, error) {
1✔
897

1✔
898
        // We check if we have a revoked state at this state num that matches
1✔
899
        // the spend transaction.
1✔
900
        spendHeight := uint32(commitSpend.SpendingHeight)
1✔
901
        retribution, err := lnwallet.NewBreachRetribution(
1✔
902
                c.cfg.chanState, broadcastStateNum, spendHeight,
1✔
903
                commitSpend.SpendingTx, c.cfg.auxLeafStore, c.cfg.auxResolver,
1✔
904
        )
1✔
905

1✔
906
        switch {
1✔
907
        // If we had no log entry at this height, this was not a revoked state.
908
        case err == channeldb.ErrLogEntryNotFound:
1✔
909
                return false, nil
1✔
910
        case err == channeldb.ErrNoPastDeltas:
1✔
911
                return false, nil
1✔
912

913
        case err != nil:
×
914
                return false, fmt.Errorf("unable to create breach "+
×
915
                        "retribution: %v", err)
×
916
        }
917

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

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

936
        // We'll set the ConfCommitKey here as the remote htlc set. This is
937
        // only used to ensure a nil-pointer-dereference doesn't occur and is
938
        // not used otherwise. The HTLC's may not exist for the
939
        // RemotePendingHtlcSet.
940
        chainSet.commitSet.ConfCommitKey = fn.Some(RemoteHtlcSet)
1✔
941

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

955
        return true, nil
1✔
956
}
957

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

1✔
967
        log.Warnf("Remote node broadcast state #%v, "+
1✔
968
                "which is more than 1 beyond best known "+
1✔
969
                "state #%v!!! Attempting recovery...",
1✔
970
                broadcastStateNum, chainSet.remoteStateNum)
1✔
971

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

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

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

1012
        return true, nil
1✔
1013
}
1014

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

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

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

1065
                return fn.Any(c.cfg.isOurAddr, addrs)
1✔
1066
        }
1067

1068
        // Grab all of the outputs that correspond with our delivery address
1069
        // or our wallet is aware of.
1070
        outs := fn.Filter(fn.PredOr(isDeliveryOutput, isWalletOutput), tx.TxOut)
1✔
1071

1✔
1072
        // Grab the values for those outputs.
1✔
1073
        vals := fn.Map(func(o *wire.TxOut) int64 { return o.Value }, outs)
2✔
1074

1075
        // Return the sum.
1076
        return btcutil.Amount(fn.Sum(vals))
1✔
1077
}
1078

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

1✔
1087
        log.Infof("Cooperative closure for ChannelPoint(%v): %v",
1✔
1088
                c.cfg.chanState.FundingOutpoint, spew.Sdump(broadcastTx))
1✔
1089

1✔
1090
        // If the input *is* final, then we'll check to see which output is
1✔
1091
        // ours.
1✔
1092
        localAmt := c.toSelfAmount(broadcastTx)
1✔
1093

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

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

1122
        // Create a summary of all the information needed to handle the
1123
        // cooperative closure.
1124
        closeInfo := &CooperativeCloseInfo{
1✔
1125
                ChannelCloseSummary: closeSummary,
1✔
1126
        }
1✔
1127

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

1✔
1141
        return nil
1✔
1142
}
1143

1144
// dispatchLocalForceClose processes a unilateral close by us being confirmed.
1145
func (c *chainWatcher) dispatchLocalForceClose(
1146
        commitSpend *chainntnfs.SpendDetail,
1147
        stateNum uint64, commitSet CommitSet) error {
1✔
1148

1✔
1149
        log.Infof("Local unilateral close of ChannelPoint(%v) "+
1✔
1150
                "detected", c.cfg.chanState.FundingOutpoint)
1✔
1151

1✔
1152
        forceClose, err := lnwallet.NewLocalForceCloseSummary(
1✔
1153
                c.cfg.chanState, c.cfg.signer, commitSpend.SpendingTx, stateNum,
1✔
1154
                c.cfg.auxLeafStore, c.cfg.auxResolver,
1✔
1155
        )
1✔
1156
        if err != nil {
1✔
1157
                return err
×
1158
        }
×
1159

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

1✔
1178
        resolutions, err := forceClose.ContractResolutions.UnwrapOrErr(
1✔
1179
                fmt.Errorf("resolutions not found"),
1✔
1180
        )
1✔
1181
        if err != nil {
1✔
1182
                return err
×
1183
        }
×
1184

1185
        // If our commitment output isn't dust or we have active HTLC's on the
1186
        // commitment transaction, then we'll populate the balances on the
1187
        // close channel summary.
1188
        if resolutions.CommitResolution != nil {
2✔
1189
                localBalance := chanSnapshot.LocalBalance.ToSatoshis()
1✔
1190
                closeSummary.SettledBalance = localBalance
1✔
1191
                closeSummary.TimeLockedBalance = localBalance
1✔
1192
        }
1✔
1193

1194
        if resolutions.HtlcResolutions != nil {
2✔
1195
                for _, htlc := range resolutions.HtlcResolutions.OutgoingHTLCs {
2✔
1196
                        htlcValue := btcutil.Amount(
1✔
1197
                                htlc.SweepSignDesc.Output.Value,
1✔
1198
                        )
1✔
1199
                        closeSummary.TimeLockedBalance += htlcValue
1✔
1200
                }
1✔
1201
        }
1202

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

1212
        // With the event processed, we'll now notify all subscribers of the
1213
        // event.
1214
        closeInfo := &LocalUnilateralCloseInfo{
1✔
1215
                SpendDetail:            commitSpend,
1✔
1216
                LocalForceCloseSummary: forceClose,
1✔
1217
                ChannelCloseSummary:    closeSummary,
1✔
1218
                CommitSet:              commitSet,
1✔
1219
        }
1✔
1220
        c.Lock()
1✔
1221
        for _, sub := range c.clientSubscriptions {
2✔
1222
                select {
1✔
1223
                case sub.LocalUnilateralClosure <- closeInfo:
1✔
1224
                case <-c.quit:
×
1225
                        c.Unlock()
×
1226
                        return fmt.Errorf("exiting")
×
1227
                }
1228
        }
1229
        c.Unlock()
1✔
1230

1✔
1231
        return nil
1✔
1232
}
1233

1234
// dispatchRemoteForceClose processes a detected unilateral channel closure by
1235
// the remote party. This function will prepare a UnilateralCloseSummary which
1236
// will then be sent to any subscribers allowing them to resolve all our funds
1237
// in the channel on chain. Once this close summary is prepared, all registered
1238
// subscribers will receive a notification of this event. The commitPoint
1239
// argument should be set to the per_commitment_point corresponding to the
1240
// spending commitment.
1241
//
1242
// NOTE: The remoteCommit argument should be set to the stored commitment for
1243
// this particular state. If we don't have the commitment stored (should only
1244
// happen in case we have lost state) it should be set to an empty struct, in
1245
// which case we will attempt to sweep the non-HTLC output using the passed
1246
// commitPoint.
1247
func (c *chainWatcher) dispatchRemoteForceClose(
1248
        commitSpend *chainntnfs.SpendDetail,
1249
        remoteCommit channeldb.ChannelCommitment,
1250
        commitSet CommitSet, commitPoint *btcec.PublicKey) error {
1✔
1251

1✔
1252
        log.Infof("Unilateral close of ChannelPoint(%v) "+
1✔
1253
                "detected", c.cfg.chanState.FundingOutpoint)
1✔
1254

1✔
1255
        // First, we'll create a closure summary that contains all the
1✔
1256
        // materials required to let each subscriber sweep the funds in the
1✔
1257
        // channel on-chain.
1✔
1258
        uniClose, err := lnwallet.NewUnilateralCloseSummary(
1✔
1259
                c.cfg.chanState, c.cfg.signer, commitSpend, remoteCommit,
1✔
1260
                commitPoint, c.cfg.auxLeafStore, c.cfg.auxResolver,
1✔
1261
        )
1✔
1262
        if err != nil {
1✔
1263
                return err
×
1264
        }
×
1265

1266
        // With the event processed, we'll now notify all subscribers of the
1267
        // event.
1268
        c.Lock()
1✔
1269
        for _, sub := range c.clientSubscriptions {
2✔
1270
                select {
1✔
1271
                case sub.RemoteUnilateralClosure <- &RemoteUnilateralCloseInfo{
1272
                        UnilateralCloseSummary: uniClose,
1273
                        CommitSet:              commitSet,
1274
                }:
1✔
1275
                case <-c.quit:
×
1276
                        c.Unlock()
×
1277
                        return fmt.Errorf("exiting")
×
1278
                }
1279
        }
1280
        c.Unlock()
1✔
1281

1✔
1282
        return nil
1✔
1283
}
1284

1285
// dispatchContractBreach processes a detected contract breached by the remote
1286
// party. This method is to be called once we detect that the remote party has
1287
// broadcast a prior revoked commitment state. This method well prepare all the
1288
// materials required to bring the cheater to justice, then notify all
1289
// registered subscribers of this event.
1290
func (c *chainWatcher) dispatchContractBreach(spendEvent *chainntnfs.SpendDetail,
1291
        chainSet *chainSet, broadcastStateNum uint64,
1292
        retribution *lnwallet.BreachRetribution,
1293
        anchorRes *lnwallet.AnchorResolution) error {
1✔
1294

1✔
1295
        log.Warnf("Remote peer has breached the channel contract for "+
1✔
1296
                "ChannelPoint(%v). Revoked state #%v was broadcast!!!",
1✔
1297
                c.cfg.chanState.FundingOutpoint, broadcastStateNum)
1✔
1298

1✔
1299
        if err := c.cfg.chanState.MarkBorked(); err != nil {
1✔
1300
                return fmt.Errorf("unable to mark channel as borked: %w", err)
×
1301
        }
×
1302

1303
        spendHeight := uint32(spendEvent.SpendingHeight)
1✔
1304

1✔
1305
        log.Debugf("Punishment breach retribution created: %v",
1✔
1306
                lnutils.NewLogClosure(func() string {
2✔
1307
                        retribution.KeyRing.LocalHtlcKey = nil
1✔
1308
                        retribution.KeyRing.RemoteHtlcKey = nil
1✔
1309
                        retribution.KeyRing.ToLocalKey = nil
1✔
1310
                        retribution.KeyRing.ToRemoteKey = nil
1✔
1311
                        retribution.KeyRing.RevocationKey = nil
1✔
1312
                        return spew.Sdump(retribution)
1✔
1313
                }))
1✔
1314

1315
        settledBalance := chainSet.remoteCommit.LocalBalance.ToSatoshis()
1✔
1316
        closeSummary := channeldb.ChannelCloseSummary{
1✔
1317
                ChanPoint:               c.cfg.chanState.FundingOutpoint,
1✔
1318
                ChainHash:               c.cfg.chanState.ChainHash,
1✔
1319
                ClosingTXID:             *spendEvent.SpenderTxHash,
1✔
1320
                CloseHeight:             spendHeight,
1✔
1321
                RemotePub:               c.cfg.chanState.IdentityPub,
1✔
1322
                Capacity:                c.cfg.chanState.Capacity,
1✔
1323
                SettledBalance:          settledBalance,
1✔
1324
                CloseType:               channeldb.BreachClose,
1✔
1325
                IsPending:               true,
1✔
1326
                ShortChanID:             c.cfg.chanState.ShortChanID(),
1✔
1327
                RemoteCurrentRevocation: c.cfg.chanState.RemoteCurrentRevocation,
1✔
1328
                RemoteNextRevocation:    c.cfg.chanState.RemoteNextRevocation,
1✔
1329
                LocalChanConfig:         c.cfg.chanState.LocalChanCfg,
1✔
1330
        }
1✔
1331

1✔
1332
        // Attempt to add a channel sync message to the close summary.
1✔
1333
        chanSync, err := c.cfg.chanState.ChanSyncMsg()
1✔
1334
        if err != nil {
1✔
1335
                log.Errorf("ChannelPoint(%v): unable to create channel sync "+
×
1336
                        "message: %v", c.cfg.chanState.FundingOutpoint, err)
×
1337
        } else {
1✔
1338
                closeSummary.LastChanSyncMsg = chanSync
1✔
1339
        }
1✔
1340

1341
        // Hand the retribution info over to the BreachArbitrator. This function
1342
        // will wait for a response from the breach arbiter and then proceed to
1343
        // send a BreachCloseInfo to the channel arbitrator. The channel arb
1344
        // will then mark the channel as closed after resolutions and the
1345
        // commit set are logged in the arbitrator log.
1346
        if err := c.cfg.contractBreach(retribution); err != nil {
1✔
1347
                log.Errorf("unable to hand breached contract off to "+
×
1348
                        "BreachArbitrator: %v", err)
×
1349
                return err
×
1350
        }
×
1351

1352
        breachRes := &BreachResolution{
1✔
1353
                FundingOutPoint: c.cfg.chanState.FundingOutpoint,
1✔
1354
        }
1✔
1355

1✔
1356
        breachInfo := &BreachCloseInfo{
1✔
1357
                CommitHash:       spendEvent.SpendingTx.TxHash(),
1✔
1358
                BreachResolution: breachRes,
1✔
1359
                AnchorResolution: anchorRes,
1✔
1360
                CommitSet:        chainSet.commitSet,
1✔
1361
                CloseSummary:     closeSummary,
1✔
1362
        }
1✔
1363

1✔
1364
        // With the event processed and channel closed, we'll now notify all
1✔
1365
        // subscribers of the event.
1✔
1366
        c.Lock()
1✔
1367
        for _, sub := range c.clientSubscriptions {
2✔
1368
                select {
1✔
1369
                case sub.ContractBreach <- breachInfo:
1✔
1370
                case <-c.quit:
×
1371
                        c.Unlock()
×
1372
                        return fmt.Errorf("quitting")
×
1373
                }
1374
        }
1375
        c.Unlock()
1✔
1376

1✔
1377
        return nil
1✔
1378
}
1379

1380
// waitForCommitmentPoint waits for the commitment point to be inserted into
1381
// the local database. We'll use this method in the DLP case, to wait for the
1382
// remote party to send us their point, as we can't proceed until we have that.
UNCOV
1383
func (c *chainWatcher) waitForCommitmentPoint() *btcec.PublicKey {
×
UNCOV
1384
        // If we are lucky, the remote peer sent us the correct commitment
×
UNCOV
1385
        // point during channel sync, such that we can sweep our funds. If we
×
UNCOV
1386
        // cannot find the commit point, there's not much we can do other than
×
UNCOV
1387
        // wait for us to retrieve it. We will attempt to retrieve it from the
×
UNCOV
1388
        // peer each time we connect to it.
×
UNCOV
1389
        //
×
UNCOV
1390
        // TODO(halseth): actively initiate re-connection to the peer?
×
UNCOV
1391
        backoff := minCommitPointPollTimeout
×
UNCOV
1392
        for {
×
UNCOV
1393
                commitPoint, err := c.cfg.chanState.DataLossCommitPoint()
×
UNCOV
1394
                if err == nil {
×
UNCOV
1395
                        return commitPoint
×
UNCOV
1396
                }
×
1397

1398
                log.Errorf("Unable to retrieve commitment point for "+
×
1399
                        "channel(%v) with lost state: %v. Retrying in %v.",
×
1400
                        c.cfg.chanState.FundingOutpoint, err, backoff)
×
1401

×
1402
                select {
×
1403
                // Wait before retrying, with an exponential backoff.
1404
                case <-time.After(backoff):
×
1405
                        backoff = 2 * backoff
×
1406
                        if backoff > maxCommitPointPollTimeout {
×
1407
                                backoff = maxCommitPointPollTimeout
×
1408
                        }
×
1409

1410
                case <-c.quit:
×
1411
                        return nil
×
1412
                }
1413
        }
1414
}
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