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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

69.35
/contractcourt/breach_arbitrator.go
1
package contractcourt
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "errors"
7
        "fmt"
8
        "io"
9
        "sync"
10

11
        "github.com/btcsuite/btcd/blockchain"
12
        "github.com/btcsuite/btcd/btcutil"
13
        "github.com/btcsuite/btcd/chaincfg/chainhash"
14
        "github.com/btcsuite/btcd/txscript"
15
        "github.com/btcsuite/btcd/wire"
16
        "github.com/lightningnetwork/lnd/chainntnfs"
17
        "github.com/lightningnetwork/lnd/channeldb"
18
        "github.com/lightningnetwork/lnd/fn/v2"
19
        graphdb "github.com/lightningnetwork/lnd/graph/db"
20
        "github.com/lightningnetwork/lnd/input"
21
        "github.com/lightningnetwork/lnd/kvdb"
22
        "github.com/lightningnetwork/lnd/labels"
23
        "github.com/lightningnetwork/lnd/lntypes"
24
        "github.com/lightningnetwork/lnd/lnutils"
25
        "github.com/lightningnetwork/lnd/lnwallet"
26
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
27
        "github.com/lightningnetwork/lnd/sweep"
28
        "github.com/lightningnetwork/lnd/tlv"
29
)
30

31
const (
32
        // justiceTxConfTarget is the number of blocks we'll use as a
33
        // confirmation target when creating the justice transaction. We'll
34
        // choose an aggressive target, since we want to be sure it confirms
35
        // quickly.
36
        justiceTxConfTarget = 2
37

38
        // blocksPassedSplitPublish is the number of blocks without
39
        // confirmation of the justice tx we'll wait before starting to publish
40
        // smaller variants of the justice tx. We do this to mitigate an attack
41
        // the channel peer can do by pinning the HTLC outputs of the
42
        // commitment with low-fee HTLC transactions.
43
        blocksPassedSplitPublish = 4
44
)
45

46
var (
47
        // retributionBucket stores retribution state on disk between detecting
48
        // a contract breach, broadcasting a justice transaction that sweeps the
49
        // channel, and finally witnessing the justice transaction confirm on
50
        // the blockchain. It is critical that such state is persisted on disk,
51
        // so that if our node restarts at any point during the retribution
52
        // procedure, we can recover and continue from the persisted state.
53
        retributionBucket = []byte("retribution")
54

55
        // taprootRetributionBucket stores the tarpoot specific retribution
56
        // information. This includes things like the control blocks for both
57
        // commitment outputs, and the taptweak needed to sweep each HTLC (one
58
        // for the first and one for the second level).
59
        taprootRetributionBucket = []byte("tap-retribution")
60

61
        // errBrarShuttingDown is an error returned if the BreachArbitrator has
62
        // been signalled to exit.
63
        errBrarShuttingDown = errors.New("BreachArbitrator shutting down")
64
)
65

66
// ContractBreachEvent is an event the BreachArbitrator will receive in case a
67
// contract breach is observed on-chain. It contains the necessary information
68
// to handle the breach, and a ProcessACK closure we will use to ACK the event
69
// when we have safely stored all the necessary information.
70
type ContractBreachEvent struct {
71
        // ChanPoint is the channel point of the breached channel.
72
        ChanPoint wire.OutPoint
73

74
        // ProcessACK is an closure that should be called with a nil error iff
75
        // the breach retribution info is safely stored in the retribution
76
        // store. In case storing the information to the store fails, a non-nil
77
        // error should be used. When this closure returns, it means that the
78
        // contract court has marked the channel pending close in the DB, and
79
        // it is safe for the BreachArbitrator to carry on its duty.
80
        ProcessACK func(error)
81

82
        // BreachRetribution is the information needed to act on this contract
83
        // breach.
84
        BreachRetribution *lnwallet.BreachRetribution
85
}
86

87
// ChannelCloseType is an enum which signals the type of channel closure the
88
// peer should execute.
89
type ChannelCloseType uint8
90

91
const (
92
        // CloseRegular indicates a regular cooperative channel closure
93
        // should be attempted.
94
        CloseRegular ChannelCloseType = iota
95

96
        // CloseBreach indicates that a channel breach has been detected, and
97
        // the link should immediately be marked as unavailable.
98
        CloseBreach
99
)
100

101
// RetributionStorer provides an interface for managing a persistent map from
102
// wire.OutPoint -> retributionInfo. Upon learning of a breach, a
103
// BreachArbitrator should record the retributionInfo for the breached channel,
104
// which serves a checkpoint in the event that retribution needs to be resumed
105
// after failure. A RetributionStore provides an interface for managing the
106
// persisted set, as well as mapping user defined functions over the entire
107
// on-disk contents.
108
//
109
// Calls to RetributionStore may occur concurrently. A concrete instance of
110
// RetributionStore should use appropriate synchronization primitives, or
111
// be otherwise safe for concurrent access.
112
type RetributionStorer interface {
113
        // Add persists the retributionInfo to disk, using the information's
114
        // chanPoint as the key. This method should overwrite any existing
115
        // entries found under the same key, and an error should be raised if
116
        // the addition fails.
117
        Add(retInfo *retributionInfo) error
118

119
        // IsBreached queries the retribution store to see if the breach arbiter
120
        // is aware of any breaches for the provided channel point.
121
        IsBreached(chanPoint *wire.OutPoint) (bool, error)
122

123
        // Remove deletes the retributionInfo from disk, if any exists, under
124
        // the given key. An error should be re raised if the removal fails.
125
        Remove(key *wire.OutPoint) error
126

127
        // ForAll iterates over the existing on-disk contents and applies a
128
        // chosen, read-only callback to each. This method should ensure that it
129
        // immediately propagate any errors generated by the callback.
130
        ForAll(cb func(*retributionInfo) error, reset func()) error
131
}
132

133
// BreachConfig bundles the required subsystems used by the breach arbiter. An
134
// instance of BreachConfig is passed to NewBreachArbitrator during
135
// instantiation.
136
type BreachConfig struct {
137
        // CloseLink allows the breach arbiter to shutdown any channel links for
138
        // which it detects a breach, ensuring now further activity will
139
        // continue across the link. The method accepts link's channel point and
140
        // a close type to be included in the channel close summary.
141
        CloseLink func(*wire.OutPoint, ChannelCloseType)
142

143
        // DB provides access to the user's channels, allowing the breach
144
        // arbiter to determine the current state of a user's channels, and how
145
        // it should respond to channel closure.
146
        DB *channeldb.ChannelStateDB
147

148
        // Estimator is used by the breach arbiter to determine an appropriate
149
        // fee level when generating, signing, and broadcasting sweep
150
        // transactions.
151
        Estimator chainfee.Estimator
152

153
        // GenSweepScript generates the receiving scripts for swept outputs.
154
        GenSweepScript func() fn.Result[lnwallet.AddrWithKey]
155

156
        // Notifier provides a publish/subscribe interface for event driven
157
        // notifications regarding the confirmation of txids.
158
        Notifier chainntnfs.ChainNotifier
159

160
        // PublishTransaction facilitates the process of broadcasting a
161
        // transaction to the network.
162
        PublishTransaction func(*wire.MsgTx, string) error
163

164
        // ContractBreaches is a channel where the BreachArbitrator will receive
165
        // notifications in the event of a contract breach being observed. A
166
        // ContractBreachEvent must be ACKed by the BreachArbitrator, such that
167
        // the sending subsystem knows that the event is properly handed off.
168
        ContractBreaches <-chan *ContractBreachEvent
169

170
        // Signer is used by the breach arbiter to generate sweep transactions,
171
        // which move coins from previously open channels back to the user's
172
        // wallet.
173
        Signer input.Signer
174

175
        // Store is a persistent resource that maintains information regarding
176
        // breached channels. This is used in conjunction with DB to recover
177
        // from crashes, restarts, or other failures.
178
        Store RetributionStorer
179

180
        // AuxSweeper is an optional interface that can be used to modify the
181
        // way sweep transaction are generated.
182
        AuxSweeper fn.Option[sweep.AuxSweeper]
183
}
184

185
// BreachArbitrator is a special subsystem which is responsible for watching and
186
// acting on the detection of any attempted uncooperative channel breaches by
187
// channel counterparties. This file essentially acts as deterrence code for
188
// those attempting to launch attacks against the daemon. In practice it's
189
// expected that the logic in this file never gets executed, but it is
190
// important to have it in place just in case we encounter cheating channel
191
// counterparties.
192
// TODO(roasbeef): closures in config for subsystem pointers to decouple?
193
type BreachArbitrator struct {
194
        started sync.Once
195
        stopped sync.Once
196

197
        cfg *BreachConfig
198

199
        subscriptions map[wire.OutPoint]chan struct{}
200

201
        quit chan struct{}
202
        wg   sync.WaitGroup
203
        sync.Mutex
204
}
205

206
// NewBreachArbitrator creates a new instance of a BreachArbitrator initialized
207
// with its dependent objects.
208
func NewBreachArbitrator(cfg *BreachConfig) *BreachArbitrator {
8✔
209
        return &BreachArbitrator{
8✔
210
                cfg:           cfg,
8✔
211
                subscriptions: make(map[wire.OutPoint]chan struct{}),
8✔
212
                quit:          make(chan struct{}),
8✔
213
        }
8✔
214
}
8✔
215

216
// Start is an idempotent method that officially starts the BreachArbitrator
217
// along with all other goroutines it needs to perform its functions.
218
func (b *BreachArbitrator) Start() error {
8✔
219
        var err error
8✔
220
        b.started.Do(func() {
16✔
221
                brarLog.Info("Breach arbiter starting")
8✔
222
                err = b.start()
8✔
223
        })
8✔
224
        return err
8✔
225
}
226

227
func (b *BreachArbitrator) start() error {
8✔
228
        // Load all retributions currently persisted in the retribution store.
8✔
229
        var breachRetInfos map[wire.OutPoint]retributionInfo
8✔
230
        if err := b.cfg.Store.ForAll(func(ret *retributionInfo) error {
8✔
231
                breachRetInfos[ret.chanPoint] = *ret
×
232
                return nil
×
233
        }, func() {
8✔
234
                breachRetInfos = make(map[wire.OutPoint]retributionInfo)
8✔
235
        }); err != nil {
8✔
236
                brarLog.Errorf("Unable to create retribution info: %v", err)
×
237
                return err
×
238
        }
×
239

240
        // Load all currently closed channels from disk, we will use the
241
        // channels that have been marked fully closed to filter the retribution
242
        // information loaded from disk. This is necessary in the event that the
243
        // channel was marked fully closed, but was not removed from the
244
        // retribution store.
245
        closedChans, err := b.cfg.DB.FetchClosedChannels(false)
8✔
246
        if err != nil {
8✔
247
                brarLog.Errorf("Unable to fetch closing channels: %v", err)
×
248
                return err
×
249
        }
×
250

251
        brarLog.Debugf("Found %v closing channels, %v retribution records",
8✔
252
                len(closedChans), len(breachRetInfos))
8✔
253

8✔
254
        // Using the set of non-pending, closed channels, reconcile any
8✔
255
        // discrepancies between the channeldb and the retribution store by
8✔
256
        // removing any retribution information for which we have already
8✔
257
        // finished our responsibilities. If the removal is successful, we also
8✔
258
        // remove the entry from our in-memory map, to avoid any further action
8✔
259
        // for this channel.
8✔
260
        // TODO(halseth): no need continue on IsPending once closed channels
8✔
261
        // actually means close transaction is confirmed.
8✔
262
        for _, chanSummary := range closedChans {
8✔
263
                brarLog.Debugf("Working on close channel: %v, is_pending: %v",
×
264
                        chanSummary.ChanPoint, chanSummary.IsPending)
×
265

×
266
                if chanSummary.IsPending {
×
267
                        continue
×
268
                }
269

270
                chanPoint := &chanSummary.ChanPoint
×
271
                if _, ok := breachRetInfos[*chanPoint]; ok {
×
272
                        if err := b.cfg.Store.Remove(chanPoint); err != nil {
×
273
                                brarLog.Errorf("Unable to remove closed "+
×
274
                                        "chanid=%v from breach arbiter: %v",
×
275
                                        chanPoint, err)
×
276
                                return err
×
277
                        }
×
278
                        delete(breachRetInfos, *chanPoint)
×
279

×
280
                        brarLog.Debugf("Skipped closed channel: %v",
×
281
                                chanSummary.ChanPoint)
×
282
                }
283
        }
284

285
        // Spawn the exactRetribution tasks to monitor and resolve any breaches
286
        // that were loaded from the retribution store.
287
        for chanPoint := range breachRetInfos {
8✔
288
                retInfo := breachRetInfos[chanPoint]
×
289

×
290
                brarLog.Debugf("Handling breach handoff on startup "+
×
291
                        "for ChannelPoint(%v)", chanPoint)
×
292

×
293
                // Register for a notification when the breach transaction is
×
294
                // confirmed on chain.
×
295
                breachTXID := retInfo.commitHash
×
296
                breachScript := retInfo.breachedOutputs[0].signDesc.Output.PkScript
×
297
                confChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
×
298
                        &breachTXID, breachScript, 1, retInfo.breachHeight,
×
299
                )
×
300
                if err != nil {
×
301
                        brarLog.Errorf("Unable to register for conf updates "+
×
302
                                "for txid: %v, err: %v", breachTXID, err)
×
303
                        return err
×
304
                }
×
305

306
                // Launch a new goroutine which to finalize the channel
307
                // retribution after the breach transaction confirms.
308
                b.wg.Add(1)
×
309
                go b.exactRetribution(confChan, &retInfo)
×
310
        }
311

312
        // Start watching the remaining active channels!
313
        b.wg.Add(1)
8✔
314
        go b.contractObserver()
8✔
315

8✔
316
        return nil
8✔
317
}
318

319
// Stop is an idempotent method that signals the BreachArbitrator to execute a
320
// graceful shutdown. This function will block until all goroutines spawned by
321
// the BreachArbitrator have gracefully exited.
322
func (b *BreachArbitrator) Stop() error {
8✔
323
        b.stopped.Do(func() {
16✔
324
                brarLog.Infof("Breach arbiter shutting down...")
8✔
325
                defer brarLog.Debug("Breach arbiter shutdown complete")
8✔
326

8✔
327
                close(b.quit)
8✔
328
                b.wg.Wait()
8✔
329
        })
8✔
330
        return nil
8✔
331
}
332

333
// IsBreached queries the breach arbiter's retribution store to see if it is
334
// aware of any channel breaches for a particular channel point.
335
func (b *BreachArbitrator) IsBreached(chanPoint *wire.OutPoint) (bool, error) {
12✔
336
        return b.cfg.Store.IsBreached(chanPoint)
12✔
337
}
12✔
338

339
// SubscribeBreachComplete is used by outside subsystems to be notified of a
340
// successful breach resolution.
341
func (b *BreachArbitrator) SubscribeBreachComplete(chanPoint *wire.OutPoint,
342
        c chan struct{}) (bool, error) {
×
343

×
344
        breached, err := b.cfg.Store.IsBreached(chanPoint)
×
345
        if err != nil {
×
346
                // If an error occurs, no subscription will be registered.
×
347
                return false, err
×
348
        }
×
349

350
        if !breached {
×
351
                // If chanPoint no longer exists in the Store, then the breach
×
352
                // was cleaned up successfully. Any subscription that occurs
×
353
                // happens after the breach information was persisted to the
×
354
                // underlying store.
×
355
                return true, nil
×
356
        }
×
357

358
        // Otherwise since the channel point is not resolved, add a
359
        // subscription. There can only be one subscription per channel point.
360
        b.Lock()
×
361
        defer b.Unlock()
×
362
        b.subscriptions[*chanPoint] = c
×
363

×
364
        return false, nil
×
365
}
366

367
// notifyBreachComplete is used by the BreachArbitrator to notify outside
368
// subsystems that the breach resolution process is complete.
369
func (b *BreachArbitrator) notifyBreachComplete(chanPoint *wire.OutPoint) {
4✔
370
        b.Lock()
4✔
371
        defer b.Unlock()
4✔
372
        if c, ok := b.subscriptions[*chanPoint]; ok {
4✔
373
                close(c)
×
374
        }
×
375

376
        // Remove the subscription.
377
        delete(b.subscriptions, *chanPoint)
4✔
378
}
379

380
// contractObserver is the primary goroutine for the BreachArbitrator. This
381
// goroutine is responsible for handling breach events coming from the
382
// contractcourt on the ContractBreaches channel. If a channel breach is
383
// detected, then the contractObserver will execute the retribution logic
384
// required to sweep ALL outputs from a contested channel into the daemon's
385
// wallet.
386
//
387
// NOTE: This MUST be run as a goroutine.
388
func (b *BreachArbitrator) contractObserver() {
8✔
389
        defer b.wg.Done()
8✔
390

8✔
391
        brarLog.Infof("Starting contract observer, watching for breaches.")
8✔
392

8✔
393
        for {
24✔
394
                select {
16✔
395
                case breachEvent := <-b.cfg.ContractBreaches:
8✔
396
                        // We have been notified about a contract breach!
8✔
397
                        // Handle the handoff, making sure we ACK the event
8✔
398
                        // after we have safely added it to the retribution
8✔
399
                        // store.
8✔
400
                        b.wg.Add(1)
8✔
401
                        go b.handleBreachHandoff(breachEvent)
8✔
402

403
                case <-b.quit:
8✔
404
                        return
8✔
405
                }
406
        }
407
}
408

409
// spend is used to wrap the index of the retributionInfo output that gets
410
// spent together with the spend details.
411
type spend struct {
412
        index  int
413
        detail *chainntnfs.SpendDetail
414
}
415

416
// waitForSpendEvent waits for any of the breached outputs to get spent, and
417
// returns the spend details for those outputs. The spendNtfns map is a cache
418
// used to store registered spend subscriptions, in case we must call this
419
// method multiple times.
420
func (b *BreachArbitrator) waitForSpendEvent(breachInfo *retributionInfo,
421
        spendNtfns map[wire.OutPoint]*chainntnfs.SpendEvent) ([]spend, error) {
13✔
422

13✔
423
        inputs := breachInfo.breachedOutputs
13✔
424

13✔
425
        // We create a channel the first goroutine that gets a spend event can
13✔
426
        // signal. We make it buffered in case multiple spend events come in at
13✔
427
        // the same time.
13✔
428
        anySpend := make(chan struct{}, len(inputs))
13✔
429

13✔
430
        // The allSpends channel will be used to pass spend events from all the
13✔
431
        // goroutines that detects a spend before they are signalled to exit.
13✔
432
        allSpends := make(chan spend, len(inputs))
13✔
433

13✔
434
        // exit will be used to signal the goroutines that they can exit.
13✔
435
        exit := make(chan struct{})
13✔
436
        var wg sync.WaitGroup
13✔
437

13✔
438
        // We'll now launch a goroutine for each of the HTLC outputs, that will
13✔
439
        // signal the moment they detect a spend event.
13✔
440
        for i := range inputs {
40✔
441
                breachedOutput := &inputs[i]
27✔
442

27✔
443
                brarLog.Infof("Checking spend from %v(%v) for ChannelPoint(%v)",
27✔
444
                        breachedOutput.witnessType, breachedOutput.outpoint,
27✔
445
                        breachInfo.chanPoint)
27✔
446

27✔
447
                // If we have already registered for a notification for this
27✔
448
                // output, we'll reuse it.
27✔
449
                spendNtfn, ok := spendNtfns[breachedOutput.outpoint]
27✔
450
                if !ok {
41✔
451
                        var err error
14✔
452
                        spendNtfn, err = b.cfg.Notifier.RegisterSpendNtfn(
14✔
453
                                &breachedOutput.outpoint,
14✔
454
                                breachedOutput.signDesc.Output.PkScript,
14✔
455
                                breachInfo.breachHeight,
14✔
456
                        )
14✔
457
                        if err != nil {
14✔
458
                                brarLog.Errorf("Unable to check for spentness "+
×
459
                                        "of outpoint=%v: %v",
×
460
                                        breachedOutput.outpoint, err)
×
461

×
462
                                // Registration may have failed if we've been
×
463
                                // instructed to shutdown. If so, return here
×
464
                                // to avoid entering an infinite loop.
×
465
                                select {
×
466
                                case <-b.quit:
×
467
                                        return nil, errBrarShuttingDown
×
468
                                default:
×
469
                                        continue
×
470
                                }
471
                        }
472
                        spendNtfns[breachedOutput.outpoint] = spendNtfn
14✔
473
                }
474

475
                // Launch a goroutine waiting for a spend event.
476
                b.wg.Add(1)
27✔
477
                wg.Add(1)
27✔
478
                go func(index int, spendEv *chainntnfs.SpendEvent) {
54✔
479
                        defer b.wg.Done()
27✔
480
                        defer wg.Done()
27✔
481

27✔
482
                        select {
27✔
483
                        // The output has been taken to the second level!
484
                        case sp, ok := <-spendEv.Spend:
14✔
485
                                if !ok {
14✔
486
                                        return
×
487
                                }
×
488

489
                                brarLog.Infof("Detected spend on %s(%v) by "+
14✔
490
                                        "txid(%v) for ChannelPoint(%v)",
14✔
491
                                        inputs[index].witnessType,
14✔
492
                                        inputs[index].outpoint,
14✔
493
                                        sp.SpenderTxHash,
14✔
494
                                        breachInfo.chanPoint)
14✔
495

14✔
496
                                // First we send the spend event on the
14✔
497
                                // allSpends channel, such that it can be
14✔
498
                                // handled after all go routines have exited.
14✔
499
                                allSpends <- spend{index, sp}
14✔
500

14✔
501
                                // Finally we'll signal the anySpend channel
14✔
502
                                // that a spend was detected, such that the
14✔
503
                                // other goroutines can be shut down.
14✔
504
                                anySpend <- struct{}{}
14✔
505
                        case <-exit:
13✔
506
                                return
13✔
507
                        case <-b.quit:
×
508
                                return
×
509
                        }
510
                }(i, spendNtfn)
511
        }
512

513
        // We'll wait for any of the outputs to be spent, or that we are
514
        // signalled to exit.
515
        select {
13✔
516
        // A goroutine have signalled that a spend occurred.
517
        case <-anySpend:
13✔
518
                // Signal for the remaining goroutines to exit.
13✔
519
                close(exit)
13✔
520
                wg.Wait()
13✔
521

13✔
522
                // At this point all goroutines that can send on the allSpends
13✔
523
                // channel have exited. We can therefore safely close the
13✔
524
                // channel before ranging over its content.
13✔
525
                close(allSpends)
13✔
526

13✔
527
                // Gather all detected spends and return them.
13✔
528
                var spends []spend
13✔
529
                for s := range allSpends {
27✔
530
                        breachedOutput := &inputs[s.index]
14✔
531
                        delete(spendNtfns, breachedOutput.outpoint)
14✔
532

14✔
533
                        spends = append(spends, s)
14✔
534
                }
14✔
535

536
                return spends, nil
13✔
537

538
        case <-b.quit:
×
539
                return nil, errBrarShuttingDown
×
540
        }
541
}
542

543
// convertToSecondLevelRevoke takes a breached output, and a transaction that
544
// spends it to the second level, and mutates the breach output into one that
545
// is able to properly sweep that second level output. We'll use this function
546
// when we go to sweep a breached commitment transaction, but the cheating
547
// party has already attempted to take it to the second level.
548
func convertToSecondLevelRevoke(bo *breachedOutput, breachInfo *retributionInfo,
549
        spendDetails *chainntnfs.SpendDetail) {
2✔
550

2✔
551
        // In this case, we'll modify the witness type of this output to
2✔
552
        // actually prepare for a second level revoke.
2✔
553
        isTaproot := txscript.IsPayToTaproot(bo.signDesc.Output.PkScript)
2✔
554
        if isTaproot {
2✔
555
                bo.witnessType = input.TaprootHtlcSecondLevelRevoke
×
556
        } else {
2✔
557
                bo.witnessType = input.HtlcSecondLevelRevoke
2✔
558
        }
2✔
559

560
        // We'll also redirect the outpoint to this second level output, so the
561
        // spending transaction updates it inputs accordingly.
562
        spendingTx := spendDetails.SpendingTx
2✔
563
        spendInputIndex := spendDetails.SpenderInputIndex
2✔
564
        oldOp := bo.outpoint
2✔
565
        bo.outpoint = wire.OutPoint{
2✔
566
                Hash:  spendingTx.TxHash(),
2✔
567
                Index: spendInputIndex,
2✔
568
        }
2✔
569

2✔
570
        // Next, we need to update the amount so we can do fee estimation
2✔
571
        // properly, and also so we can generate a valid signature as we need
2✔
572
        // to know the new input value (the second level transactions shaves
2✔
573
        // off some funds to fees).
2✔
574
        newAmt := spendingTx.TxOut[spendInputIndex].Value
2✔
575
        bo.amt = btcutil.Amount(newAmt)
2✔
576
        bo.signDesc.Output.Value = newAmt
2✔
577
        bo.signDesc.Output.PkScript = spendingTx.TxOut[spendInputIndex].PkScript
2✔
578

2✔
579
        // For taproot outputs, the taptweak also needs to be swapped out. We
2✔
580
        // do this unconditionally as this field isn't used at all for segwit
2✔
581
        // v0 outputs.
2✔
582
        bo.signDesc.TapTweak = bo.secondLevelTapTweak[:]
2✔
583

2✔
584
        // Finally, we'll need to adjust the witness program in the
2✔
585
        // SignDescriptor.
2✔
586
        bo.signDesc.WitnessScript = bo.secondLevelWitnessScript
2✔
587

2✔
588
        brarLog.Warnf("HTLC(%v) for ChannelPoint(%v) has been spent to the "+
2✔
589
                "second-level, adjusting -> %v", oldOp, breachInfo.chanPoint,
2✔
590
                bo.outpoint)
2✔
591
}
592

593
// updateBreachInfo mutates the passed breachInfo by removing or converting any
594
// outputs among the spends. It also counts the total and revoked funds swept
595
// by our justice spends.
596
func updateBreachInfo(breachInfo *retributionInfo, spends []spend) (
597
        btcutil.Amount, btcutil.Amount) {
13✔
598

13✔
599
        inputs := breachInfo.breachedOutputs
13✔
600
        doneOutputs := make(map[int]struct{})
13✔
601

13✔
602
        var totalFunds, revokedFunds btcutil.Amount
13✔
603
        for _, s := range spends {
27✔
604
                breachedOutput := &inputs[s.index]
14✔
605
                txIn := s.detail.SpendingTx.TxIn[s.detail.SpenderInputIndex]
14✔
606

14✔
607
                switch breachedOutput.witnessType {
14✔
608
                case input.TaprootHtlcAcceptedRevoke:
×
609
                        fallthrough
×
610
                case input.TaprootHtlcOfferedRevoke:
×
611
                        fallthrough
×
612
                case input.HtlcAcceptedRevoke:
×
613
                        fallthrough
×
614
                case input.HtlcOfferedRevoke:
4✔
615
                        // If the HTLC output was spent using the revocation
4✔
616
                        // key, it is our own spend, and we can forget the
4✔
617
                        // output. Otherwise it has been taken to the second
4✔
618
                        // level.
4✔
619
                        signDesc := &breachedOutput.signDesc
4✔
620
                        ok, err := input.IsHtlcSpendRevoke(txIn, signDesc)
4✔
621
                        if err != nil {
4✔
622
                                brarLog.Errorf("Unable to determine if "+
×
623
                                        "revoke spend: %v", err)
×
624
                                break
×
625
                        }
626

627
                        if ok {
6✔
628
                                brarLog.Debugf("HTLC spend was our own " +
2✔
629
                                        "revocation spend")
2✔
630
                                break
2✔
631
                        }
632

633
                        brarLog.Infof("Spend on second-level "+
2✔
634
                                "%s(%v) for ChannelPoint(%v) "+
2✔
635
                                "transitions to second-level output",
2✔
636
                                breachedOutput.witnessType,
2✔
637
                                breachedOutput.outpoint, breachInfo.chanPoint)
2✔
638

2✔
639
                        // In this case we'll morph our initial revoke
2✔
640
                        // spend to instead point to the second level
2✔
641
                        // output, and update the sign descriptor in the
2✔
642
                        // process.
2✔
643
                        convertToSecondLevelRevoke(
2✔
644
                                breachedOutput, breachInfo, s.detail,
2✔
645
                        )
2✔
646

2✔
647
                        continue
2✔
648
                }
649

650
                // Now that we have determined the spend is done by us, we
651
                // count the total and revoked funds swept depending on the
652
                // input type.
653
                switch breachedOutput.witnessType {
12✔
654
                // If the output being revoked is the remote commitment output
655
                // or an offered HTLC output, its amount contributes to the
656
                // value of funds being revoked from the counter party.
657
                case input.CommitmentRevoke, input.TaprootCommitmentRevoke,
658
                        input.HtlcSecondLevelRevoke,
659
                        input.TaprootHtlcSecondLevelRevoke,
660
                        input.TaprootHtlcOfferedRevoke, input.HtlcOfferedRevoke:
8✔
661

8✔
662
                        revokedFunds += breachedOutput.Amount()
8✔
663
                }
664

665
                totalFunds += breachedOutput.Amount()
12✔
666
                brarLog.Infof("Spend on %s(%v) for ChannelPoint(%v) "+
12✔
667
                        "transitions output to terminal state, "+
12✔
668
                        "removing input from justice transaction",
12✔
669
                        breachedOutput.witnessType,
12✔
670
                        breachedOutput.outpoint, breachInfo.chanPoint)
12✔
671

12✔
672
                doneOutputs[s.index] = struct{}{}
12✔
673
        }
674

675
        // Filter the inputs for which we can no longer proceed.
676
        var nextIndex int
13✔
677
        for i := range inputs {
40✔
678
                if _, ok := doneOutputs[i]; ok {
39✔
679
                        continue
12✔
680
                }
681

682
                inputs[nextIndex] = inputs[i]
15✔
683
                nextIndex++
15✔
684
        }
685

686
        // Update our remaining set of outputs before continuing with
687
        // another attempt at publication.
688
        breachInfo.breachedOutputs = inputs[:nextIndex]
13✔
689
        return totalFunds, revokedFunds
13✔
690
}
691

692
// exactRetribution is a goroutine which is executed once a contract breach has
693
// been detected by a breachObserver. This function is responsible for
694
// punishing a counterparty for violating the channel contract by sweeping ALL
695
// the lingering funds within the channel into the daemon's wallet.
696
//
697
// NOTE: This MUST be run as a goroutine.
698
//
699
//nolint:funlen
700
func (b *BreachArbitrator) exactRetribution(
701
        confChan *chainntnfs.ConfirmationEvent, breachInfo *retributionInfo) {
6✔
702

6✔
703
        defer b.wg.Done()
6✔
704

6✔
705
        // TODO(roasbeef): state needs to be checkpointed here
6✔
706
        select {
6✔
707
        case _, ok := <-confChan.Confirmed:
4✔
708
                // If the second value is !ok, then the channel has been closed
4✔
709
                // signifying a daemon shutdown, so we exit.
4✔
710
                if !ok {
4✔
711
                        return
×
712
                }
×
713

714
                // Otherwise, if this is a real confirmation notification, then
715
                // we fall through to complete our duty.
716
        case <-b.quit:
2✔
717
                return
2✔
718
        }
719

720
        brarLog.Debugf("Breach transaction %v has been confirmed, sweeping "+
4✔
721
                "revoked funds", breachInfo.commitHash)
4✔
722

4✔
723
        // We may have to wait for some of the HTLC outputs to be spent to the
4✔
724
        // second level before broadcasting the justice tx. We'll store the
4✔
725
        // SpendEvents between each attempt to not re-register unnecessarily.
4✔
726
        spendNtfns := make(map[wire.OutPoint]*chainntnfs.SpendEvent)
4✔
727

4✔
728
        // Compute both the total value of funds being swept and the
4✔
729
        // amount of funds that were revoked from the counter party.
4✔
730
        var totalFunds, revokedFunds btcutil.Amount
4✔
731

4✔
732
justiceTxBroadcast:
4✔
733
        // With the breach transaction confirmed, we now create the
734
        // justice tx which will claim ALL the funds within the
735
        // channel.
736
        justiceTxs, err := b.createJusticeTx(breachInfo.breachedOutputs)
13✔
737
        if err != nil {
13✔
738
                brarLog.Errorf("Unable to create justice tx: %v", err)
×
739
                return
×
740
        }
×
741
        finalTx := justiceTxs.spendAll
13✔
742

13✔
743
        brarLog.Debugf("Broadcasting justice tx: %v", lnutils.SpewLogClosure(
13✔
744
                finalTx))
13✔
745

13✔
746
        // As we're about to broadcast our breach transaction, we'll notify the
13✔
747
        // aux sweeper of our broadcast attempt first.
13✔
748
        err = fn.MapOptionZ(b.cfg.AuxSweeper, func(aux sweep.AuxSweeper) error {
13✔
749
                bumpReq := sweep.BumpRequest{
×
750
                        Inputs:          finalTx.inputs,
×
751
                        DeliveryAddress: finalTx.sweepAddr,
×
752
                        ExtraTxOut:      finalTx.extraTxOut,
×
753
                }
×
754

×
755
                return aux.NotifyBroadcast(
×
756
                        &bumpReq, finalTx.justiceTx, finalTx.fee, nil,
×
757
                )
×
758
        })
×
759
        if err != nil {
13✔
760
                brarLog.Errorf("unable to notify broadcast: %w", err)
×
761
                return
×
762
        }
×
763

764
        // We'll now attempt to broadcast the transaction which finalized the
765
        // channel's retribution against the cheating counter party.
766
        label := labels.MakeLabel(labels.LabelTypeJusticeTransaction, nil)
13✔
767
        err = b.cfg.PublishTransaction(finalTx.justiceTx, label)
13✔
768
        if err != nil {
23✔
769
                brarLog.Errorf("Unable to broadcast justice tx: %v", err)
10✔
770
        }
10✔
771

772
        // Regardless of publication succeeded or not, we now wait for any of
773
        // the inputs to be spent. If any input got spent by the remote, we
774
        // must recreate our justice transaction.
775
        var (
13✔
776
                spendChan = make(chan []spend, 1)
13✔
777
                errChan   = make(chan error, 1)
13✔
778
                wg        sync.WaitGroup
13✔
779
        )
13✔
780

13✔
781
        wg.Add(1)
13✔
782
        go func() {
26✔
783
                defer wg.Done()
13✔
784

13✔
785
                spends, err := b.waitForSpendEvent(breachInfo, spendNtfns)
13✔
786
                if err != nil {
13✔
787
                        errChan <- err
×
788
                        return
×
789
                }
×
790
                spendChan <- spends
13✔
791
        }()
792

793
        // We'll also register for block notifications, such that in case our
794
        // justice tx doesn't confirm within a reasonable timeframe, we can
795
        // start to more aggressively sweep the time sensitive outputs.
796
        newBlockChan, err := b.cfg.Notifier.RegisterBlockEpochNtfn(nil)
13✔
797
        if err != nil {
13✔
798
                brarLog.Errorf("Unable to register for block notifications: %v",
×
799
                        err)
×
800
                return
×
801
        }
×
802
        defer newBlockChan.Cancel()
13✔
803

13✔
804
Loop:
13✔
805
        for {
31✔
806
                select {
18✔
807
                case spends := <-spendChan:
13✔
808
                        // Update the breach info with the new spends.
13✔
809
                        t, r := updateBreachInfo(breachInfo, spends)
13✔
810
                        totalFunds += t
13✔
811
                        revokedFunds += r
13✔
812

13✔
813
                        brarLog.Infof("%v spends from breach tx for "+
13✔
814
                                "ChannelPoint(%v) has been detected, %v "+
13✔
815
                                "revoked funds (%v total) have been claimed",
13✔
816
                                len(spends), breachInfo.chanPoint,
13✔
817
                                revokedFunds, totalFunds)
13✔
818

13✔
819
                        if len(breachInfo.breachedOutputs) == 0 {
17✔
820
                                brarLog.Infof("Justice for ChannelPoint(%v) "+
4✔
821
                                        "has been served, %v revoked funds "+
4✔
822
                                        "(%v total) have been claimed. No "+
4✔
823
                                        "more outputs to sweep, marking fully "+
4✔
824
                                        "resolved", breachInfo.chanPoint,
4✔
825
                                        revokedFunds, totalFunds)
4✔
826

4✔
827
                                err = b.cleanupBreach(&breachInfo.chanPoint)
4✔
828
                                if err != nil {
4✔
829
                                        brarLog.Errorf("Failed to cleanup "+
×
830
                                                "breached ChannelPoint(%v): %v",
×
831
                                                breachInfo.chanPoint, err)
×
832
                                }
×
833

834
                                // TODO(roasbeef): add peer to blacklist?
835

836
                                // TODO(roasbeef): close other active channels
837
                                // with offending peer
838
                                break Loop
4✔
839
                        }
840

841
                        brarLog.Infof("Attempting another justice tx "+
9✔
842
                                "with %d inputs",
9✔
843
                                len(breachInfo.breachedOutputs))
9✔
844

9✔
845
                        wg.Wait()
9✔
846
                        goto justiceTxBroadcast
9✔
847

848
                // On every new block, we check whether we should republish the
849
                // transactions.
850
                case epoch, ok := <-newBlockChan.Epochs:
5✔
851
                        if !ok {
5✔
852
                                return
×
853
                        }
×
854

855
                        // If less than four blocks have passed since the
856
                        // breach confirmed, we'll continue waiting. It was
857
                        // published with a 2-block fee estimate, so it's not
858
                        // unexpected that four blocks without confirmation can
859
                        // pass.
860
                        splitHeight := breachInfo.breachHeight +
5✔
861
                                blocksPassedSplitPublish
5✔
862
                        if uint32(epoch.Height) < splitHeight {
9✔
863
                                continue Loop
4✔
864
                        }
865

866
                        brarLog.Warnf("Block height %v arrived without "+
1✔
867
                                "justice tx confirming (breached at "+
1✔
868
                                "height %v), splitting justice tx.",
1✔
869
                                epoch.Height, breachInfo.breachHeight)
1✔
870

1✔
871
                        // Otherwise we'll attempt to publish the two separate
1✔
872
                        // justice transactions that sweeps the commitment
1✔
873
                        // outputs and the HTLC outputs separately. This is to
1✔
874
                        // mitigate the case where our "spend all" justice TX
1✔
875
                        // doesn't propagate because the HTLC outputs have been
1✔
876
                        // pinned by low fee HTLC txs.
1✔
877
                        label := labels.MakeLabel(
1✔
878
                                labels.LabelTypeJusticeTransaction, nil,
1✔
879
                        )
1✔
880
                        if justiceTxs.spendCommitOuts != nil {
2✔
881
                                tx := justiceTxs.spendCommitOuts
1✔
882

1✔
883
                                brarLog.Debugf("Broadcasting justice tx "+
1✔
884
                                        "spending commitment outs: %v",
1✔
885
                                        lnutils.SpewLogClosure(tx))
1✔
886

1✔
887
                                err = b.cfg.PublishTransaction(
1✔
888
                                        tx.justiceTx, label,
1✔
889
                                )
1✔
890
                                if err != nil {
1✔
891
                                        brarLog.Warnf("Unable to broadcast "+
×
892
                                                "commit out spending justice "+
×
893
                                                "tx: %v", err)
×
894
                                }
×
895
                        }
896

897
                        if justiceTxs.spendHTLCs != nil {
2✔
898
                                tx := justiceTxs.spendHTLCs
1✔
899

1✔
900
                                brarLog.Debugf("Broadcasting justice tx "+
1✔
901
                                        "spending HTLC outs: %v",
1✔
902
                                        lnutils.SpewLogClosure(tx))
1✔
903

1✔
904
                                err = b.cfg.PublishTransaction(
1✔
905
                                        tx.justiceTx, label,
1✔
906
                                )
1✔
907
                                if err != nil {
1✔
908
                                        brarLog.Warnf("Unable to broadcast "+
×
909
                                                "HTLC out spending justice "+
×
910
                                                "tx: %v", err)
×
911
                                }
×
912
                        }
913

914
                        for _, tx := range justiceTxs.spendSecondLevelHTLCs {
1✔
915
                                tx := tx
×
916

×
917
                                brarLog.Debugf("Broadcasting justice tx "+
×
918
                                        "spending second-level HTLC output: %v",
×
919
                                        lnutils.SpewLogClosure(tx))
×
920

×
921
                                err = b.cfg.PublishTransaction(
×
922
                                        tx.justiceTx, label,
×
923
                                )
×
924
                                if err != nil {
×
925
                                        brarLog.Warnf("Unable to broadcast "+
×
926
                                                "second-level HTLC out "+
×
927
                                                "spending justice tx: %v", err)
×
928
                                }
×
929
                        }
930

931
                case err := <-errChan:
×
932
                        if err != errBrarShuttingDown {
×
933
                                brarLog.Errorf("error waiting for "+
×
934
                                        "spend event: %v", err)
×
935
                        }
×
936
                        break Loop
×
937

938
                case <-b.quit:
×
939
                        break Loop
×
940
                }
941
        }
942

943
        // Wait for our go routine to exit.
944
        wg.Wait()
4✔
945
}
946

947
// cleanupBreach marks the given channel point as fully resolved and removes the
948
// retribution for that the channel from the retribution store.
949
func (b *BreachArbitrator) cleanupBreach(chanPoint *wire.OutPoint) error {
4✔
950
        // With the channel closed, mark it in the database as such.
4✔
951
        err := b.cfg.DB.MarkChanFullyClosed(chanPoint)
4✔
952
        if err != nil {
4✔
953
                return fmt.Errorf("unable to mark chan as closed: %w", err)
×
954
        }
×
955

956
        // Justice has been carried out; we can safely delete the retribution
957
        // info from the database.
958
        err = b.cfg.Store.Remove(chanPoint)
4✔
959
        if err != nil {
4✔
960
                return fmt.Errorf("unable to remove retribution from db: %w",
×
961
                        err)
×
962
        }
×
963

964
        // This is after the Remove call so that the chan passed in via
965
        // SubscribeBreachComplete is always notified, no matter when it is
966
        // called. Otherwise, if notifyBreachComplete was before Remove, a
967
        // very rare edge case could occur in which SubscribeBreachComplete
968
        // is called after notifyBreachComplete and before Remove, meaning the
969
        // caller would never be notified.
970
        b.notifyBreachComplete(chanPoint)
4✔
971

4✔
972
        return nil
4✔
973
}
974

975
// handleBreachHandoff handles a new breach event, by writing it to disk, then
976
// notifies the BreachArbitrator contract observer goroutine that a channel's
977
// contract has been breached by the prior counterparty. Once notified the
978
// BreachArbitrator will attempt to sweep ALL funds within the channel using the
979
// information provided within the BreachRetribution generated due to the
980
// breach of channel contract. The funds will be swept only after the breaching
981
// transaction receives a necessary number of confirmations.
982
//
983
// NOTE: This MUST be run as a goroutine.
984
func (b *BreachArbitrator) handleBreachHandoff(
985
        breachEvent *ContractBreachEvent) {
8✔
986

8✔
987
        defer b.wg.Done()
8✔
988

8✔
989
        chanPoint := breachEvent.ChanPoint
8✔
990
        brarLog.Debugf("Handling breach handoff for ChannelPoint(%v)",
8✔
991
                chanPoint)
8✔
992

8✔
993
        // A read from this channel indicates that a channel breach has been
8✔
994
        // detected! So we notify the main coordination goroutine with the
8✔
995
        // information needed to bring the counterparty to justice.
8✔
996
        breachInfo := breachEvent.BreachRetribution
8✔
997
        brarLog.Warnf("REVOKED STATE #%v FOR ChannelPoint(%v) "+
8✔
998
                "broadcast, REMOTE PEER IS DOING SOMETHING "+
8✔
999
                "SKETCHY!!!", breachInfo.RevokedStateNum,
8✔
1000
                chanPoint)
8✔
1001

8✔
1002
        // Immediately notify the HTLC switch that this link has been
8✔
1003
        // breached in order to ensure any incoming or outgoing
8✔
1004
        // multi-hop HTLCs aren't sent over this link, nor any other
8✔
1005
        // links associated with this peer.
8✔
1006
        b.cfg.CloseLink(&chanPoint, CloseBreach)
8✔
1007

8✔
1008
        // TODO(roasbeef): need to handle case of remote broadcast
8✔
1009
        // mid-local initiated state-transition, possible
8✔
1010
        // false-positive?
8✔
1011

8✔
1012
        // Acquire the mutex to ensure consistency between the call to
8✔
1013
        // IsBreached and Add below.
8✔
1014
        b.Lock()
8✔
1015

8✔
1016
        // We first check if this breach info is already added to the
8✔
1017
        // retribution store.
8✔
1018
        breached, err := b.cfg.Store.IsBreached(&chanPoint)
8✔
1019
        if err != nil {
8✔
1020
                b.Unlock()
×
1021
                brarLog.Errorf("Unable to check breach info in DB: %v", err)
×
1022

×
1023
                // Notify about the failed lookup and return.
×
1024
                breachEvent.ProcessACK(err)
×
1025
                return
×
1026
        }
×
1027

1028
        // If this channel is already marked as breached in the retribution
1029
        // store, we already have handled the handoff for this breach. In this
1030
        // case we can safely ACK the handoff, and return.
1031
        if breached {
9✔
1032
                b.Unlock()
1✔
1033
                breachEvent.ProcessACK(nil)
1✔
1034
                return
1✔
1035
        }
1✔
1036

1037
        // Using the breach information provided by the wallet and the
1038
        // channel snapshot, construct the retribution information that
1039
        // will be persisted to disk.
1040
        retInfo := newRetributionInfo(&chanPoint, breachInfo)
7✔
1041

7✔
1042
        // Persist the pending retribution state to disk.
7✔
1043
        err = b.cfg.Store.Add(retInfo)
7✔
1044
        b.Unlock()
7✔
1045
        if err != nil {
8✔
1046
                brarLog.Errorf("Unable to persist retribution "+
1✔
1047
                        "info to db: %v", err)
1✔
1048
        }
1✔
1049

1050
        // Now that the breach has been persisted, try to send an
1051
        // acknowledgment back to the close observer with the error. If
1052
        // the ack is successful, the close observer will mark the
1053
        // channel as pending-closed in the channeldb.
1054
        breachEvent.ProcessACK(err)
7✔
1055

7✔
1056
        // Bail if we failed to persist retribution info.
7✔
1057
        if err != nil {
8✔
1058
                return
1✔
1059
        }
1✔
1060

1061
        // Now that a new channel contract has been added to the retribution
1062
        // store, we first register for a notification to be dispatched once
1063
        // the breach transaction (the revoked commitment transaction) has been
1064
        // confirmed in the chain to ensure we're not dealing with a moving
1065
        // target.
1066
        breachTXID := &retInfo.commitHash
6✔
1067
        breachScript := retInfo.breachedOutputs[0].signDesc.Output.PkScript
6✔
1068
        cfChan, err := b.cfg.Notifier.RegisterConfirmationsNtfn(
6✔
1069
                breachTXID, breachScript, 1, retInfo.breachHeight,
6✔
1070
        )
6✔
1071
        if err != nil {
6✔
1072
                brarLog.Errorf("Unable to register for conf updates for "+
×
1073
                        "txid: %v, err: %v", breachTXID, err)
×
1074
                return
×
1075
        }
×
1076

1077
        brarLog.Warnf("A channel has been breached with txid: %v. Waiting "+
6✔
1078
                "for confirmation, then justice will be served!", breachTXID)
6✔
1079

6✔
1080
        // With the retribution state persisted, channel close persisted, and
6✔
1081
        // notification registered, we launch a new goroutine which will
6✔
1082
        // finalize the channel retribution after the breach transaction has
6✔
1083
        // been confirmed.
6✔
1084
        b.wg.Add(1)
6✔
1085
        go b.exactRetribution(cfChan, retInfo)
6✔
1086
}
1087

1088
// breachedOutput contains all the information needed to sweep a breached
1089
// output. A breached output is an output that we are now entitled to due to a
1090
// revoked commitment transaction being broadcast.
1091
type breachedOutput struct {
1092
        amt         btcutil.Amount
1093
        outpoint    wire.OutPoint
1094
        witnessType input.StandardWitnessType
1095
        signDesc    input.SignDescriptor
1096
        confHeight  uint32
1097

1098
        secondLevelWitnessScript []byte
1099
        secondLevelTapTweak      [32]byte
1100

1101
        witnessFunc input.WitnessGenerator
1102

1103
        resolutionBlob fn.Option[tlv.Blob]
1104

1105
        // TODO(roasbeef): function opt and hook into brar
1106
}
1107

1108
// makeBreachedOutput assembles a new breachedOutput that can be used by the
1109
// breach arbiter to construct a justice or sweep transaction.
1110
func makeBreachedOutput(outpoint *wire.OutPoint,
1111
        witnessType input.StandardWitnessType, secondLevelScript []byte,
1112
        signDescriptor *input.SignDescriptor, confHeight uint32,
1113
        resolutionBlob fn.Option[tlv.Blob]) breachedOutput {
49✔
1114

49✔
1115
        amount := signDescriptor.Output.Value
49✔
1116

49✔
1117
        return breachedOutput{
49✔
1118
                amt:                      btcutil.Amount(amount),
49✔
1119
                outpoint:                 *outpoint,
49✔
1120
                secondLevelWitnessScript: secondLevelScript,
49✔
1121
                witnessType:              witnessType,
49✔
1122
                signDesc:                 *signDescriptor,
49✔
1123
                confHeight:               confHeight,
49✔
1124
                resolutionBlob:           resolutionBlob,
49✔
1125
        }
49✔
1126
}
49✔
1127

1128
// Amount returns the number of satoshis contained in the breached output.
1129
func (bo *breachedOutput) Amount() btcutil.Amount {
172✔
1130
        return bo.amt
172✔
1131
}
172✔
1132

1133
// OutPoint returns the breached output's identifier that is to be included as a
1134
// transaction input.
1135
func (bo *breachedOutput) OutPoint() wire.OutPoint {
398✔
1136
        return bo.outpoint
398✔
1137
}
398✔
1138

1139
// RequiredTxOut returns a non-nil TxOut if input commits to a certain
1140
// transaction output. This is used in the SINGLE|ANYONECANPAY case to make
1141
// sure any presigned input is still valid by including the output.
1142
func (bo *breachedOutput) RequiredTxOut() *wire.TxOut {
×
1143
        return nil
×
1144
}
×
1145

1146
// RequiredLockTime returns whether this input commits to a tx locktime that
1147
// must be used in the transaction including it.
1148
func (bo *breachedOutput) RequiredLockTime() (uint32, bool) {
×
1149
        return 0, false
×
1150
}
×
1151

1152
// WitnessType returns the type of witness that must be generated to spend the
1153
// breached output.
1154
func (bo *breachedOutput) WitnessType() input.WitnessType {
126✔
1155
        return bo.witnessType
126✔
1156
}
126✔
1157

1158
// SignDesc returns the breached output's SignDescriptor, which is used during
1159
// signing to compute the witness.
1160
func (bo *breachedOutput) SignDesc() *input.SignDescriptor {
361✔
1161
        return &bo.signDesc
361✔
1162
}
361✔
1163

1164
// Preimage returns the preimage that was used to create the breached output.
1165
func (bo *breachedOutput) Preimage() fn.Option[lntypes.Preimage] {
×
1166
        return fn.None[lntypes.Preimage]()
×
1167
}
×
1168

1169
// CraftInputScript computes a valid witness that allows us to spend from the
1170
// breached output. It does so by first generating and memoizing the witness
1171
// generation function, which parameterized primarily by the witness type and
1172
// sign descriptor. The method then returns the witness computed by invoking
1173
// this function on the first and subsequent calls.
1174
func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx,
1175
        hashCache *txscript.TxSigHashes,
1176
        prevOutputFetcher txscript.PrevOutputFetcher,
1177
        txinIdx int) (*input.Script, error) {
71✔
1178

71✔
1179
        // First, we ensure that the witness generation function has been
71✔
1180
        // initialized for this breached output.
71✔
1181
        signDesc := bo.SignDesc()
71✔
1182
        signDesc.PrevOutputFetcher = prevOutputFetcher
71✔
1183
        bo.witnessFunc = bo.witnessType.WitnessGenerator(signer, signDesc)
71✔
1184

71✔
1185
        // Now that we have ensured that the witness generation function has
71✔
1186
        // been initialized, we can proceed to execute it and generate the
71✔
1187
        // witness for this particular breached output.
71✔
1188
        return bo.witnessFunc(txn, hashCache, txinIdx)
71✔
1189
}
71✔
1190

1191
// BlocksToMaturity returns the relative timelock, as a number of blocks, that
1192
// must be built on top of the confirmation height before the output can be
1193
// spent.
1194
func (bo *breachedOutput) BlocksToMaturity() uint32 {
68✔
1195
        // If the output is a to_remote output we can claim, and it's of the
68✔
1196
        // confirmed type (or is a taproot channel that always has the CSV 1),
68✔
1197
        // we must wait one block before claiming it.
68✔
1198
        switch bo.witnessType {
68✔
1199
        case input.CommitmentToRemoteConfirmed, input.TaprootRemoteCommitSpend:
2✔
1200
                return 1
2✔
1201
        }
1202

1203
        // All other breached outputs have no CSV delay.
1204
        return 0
66✔
1205
}
1206

1207
// HeightHint returns the minimum height at which a confirmed spending tx can
1208
// occur.
1209
func (bo *breachedOutput) HeightHint() uint32 {
×
1210
        return bo.confHeight
×
1211
}
×
1212

1213
// UnconfParent returns information about a possibly unconfirmed parent tx.
1214
func (bo *breachedOutput) UnconfParent() *input.TxInfo {
×
1215
        return nil
×
1216
}
×
1217

1218
// ResolutionBlob returns a special opaque blob to be used to sweep/resolve this
1219
// input.
1220
func (bo *breachedOutput) ResolutionBlob() fn.Option[tlv.Blob] {
37✔
1221
        return bo.resolutionBlob
37✔
1222
}
37✔
1223

1224
// Add compile-time constraint ensuring breachedOutput implements the Input
1225
// interface.
1226
var _ input.Input = (*breachedOutput)(nil)
1227

1228
// retributionInfo encapsulates all the data needed to sweep all the contested
1229
// funds within a channel whose contract has been breached by the prior
1230
// counterparty. This struct is used to create the justice transaction which
1231
// spends all outputs of the commitment transaction into an output controlled
1232
// by the wallet.
1233
type retributionInfo struct {
1234
        commitHash   chainhash.Hash
1235
        chanPoint    wire.OutPoint
1236
        chainHash    chainhash.Hash
1237
        breachHeight uint32
1238

1239
        breachedOutputs []breachedOutput
1240
}
1241

1242
// newRetributionInfo constructs a retributionInfo containing all the
1243
// information required by the breach arbiter to recover funds from breached
1244
// channels.  The information is primarily populated using the BreachRetribution
1245
// delivered by the wallet when it detects a channel breach.
1246
func newRetributionInfo(chanPoint *wire.OutPoint,
1247
        breachInfo *lnwallet.BreachRetribution) *retributionInfo {
10✔
1248

10✔
1249
        // Determine the number of second layer HTLCs we will attempt to sweep.
10✔
1250
        nHtlcs := len(breachInfo.HtlcRetributions)
10✔
1251

10✔
1252
        // Initialize a slice to hold the outputs we will attempt to sweep. The
10✔
1253
        // maximum capacity of the slice is set to 2+nHtlcs to handle the case
10✔
1254
        // where the local, remote, and all HTLCs are not dust outputs.  All
10✔
1255
        // HTLC outputs provided by the wallet are guaranteed to be non-dust,
10✔
1256
        // though the commitment outputs are conditionally added depending on
10✔
1257
        // the nil-ness of their sign descriptors.
10✔
1258
        breachedOutputs := make([]breachedOutput, 0, nHtlcs+2)
10✔
1259

10✔
1260
        isTaproot := func() bool {
20✔
1261
                if breachInfo.LocalOutputSignDesc != nil {
20✔
1262
                        return txscript.IsPayToTaproot(
10✔
1263
                                breachInfo.LocalOutputSignDesc.Output.PkScript,
10✔
1264
                        )
10✔
1265
                }
10✔
1266

1267
                return txscript.IsPayToTaproot(
×
1268
                        breachInfo.RemoteOutputSignDesc.Output.PkScript,
×
1269
                )
×
1270
        }()
1271

1272
        // First, record the breach information for the local channel point if
1273
        // it is not considered dust, which is signaled by a non-nil sign
1274
        // descriptor. Here we use CommitmentNoDelay (or
1275
        // CommitmentNoDelayTweakless for newer commitments) since this output
1276
        // belongs to us and has no time-based constraints on spending. For
1277
        // taproot channels, this is a normal spend from our output on the
1278
        // commitment of the remote party.
1279
        if breachInfo.LocalOutputSignDesc != nil {
20✔
1280
                var witnessType input.StandardWitnessType
10✔
1281
                switch {
10✔
1282
                case isTaproot:
×
1283
                        witnessType = input.TaprootRemoteCommitSpend
×
1284

1285
                case !isTaproot &&
1286
                        breachInfo.LocalOutputSignDesc.SingleTweak == nil:
10✔
1287

10✔
1288
                        witnessType = input.CommitSpendNoDelayTweakless
10✔
1289

1290
                case !isTaproot:
×
1291
                        witnessType = input.CommitmentNoDelay
×
1292
                }
1293

1294
                // If the local delay is non-zero, it means this output is of
1295
                // the confirmed to_remote type.
1296
                if !isTaproot && breachInfo.LocalDelay != 0 {
10✔
1297
                        witnessType = input.CommitmentToRemoteConfirmed
×
1298
                }
×
1299

1300
                localOutput := makeBreachedOutput(
10✔
1301
                        &breachInfo.LocalOutpoint,
10✔
1302
                        witnessType,
10✔
1303
                        // No second level script as this is a commitment
10✔
1304
                        // output.
10✔
1305
                        nil,
10✔
1306
                        breachInfo.LocalOutputSignDesc,
10✔
1307
                        breachInfo.BreachHeight,
10✔
1308
                        breachInfo.LocalResolutionBlob,
10✔
1309
                )
10✔
1310

10✔
1311
                breachedOutputs = append(breachedOutputs, localOutput)
10✔
1312
        }
1313

1314
        // Second, record the same information regarding the remote outpoint,
1315
        // again if it is not dust, which belongs to the party who tried to
1316
        // steal our money! Here we set witnessType of the breachedOutput to
1317
        // CommitmentRevoke, since we will be using a revoke key, withdrawing
1318
        // the funds from the commitment transaction immediately.
1319
        if breachInfo.RemoteOutputSignDesc != nil {
17✔
1320
                var witType input.StandardWitnessType
7✔
1321
                if isTaproot {
7✔
1322
                        witType = input.TaprootCommitmentRevoke
×
1323
                } else {
7✔
1324
                        witType = input.CommitmentRevoke
7✔
1325
                }
7✔
1326

1327
                remoteOutput := makeBreachedOutput(
7✔
1328
                        &breachInfo.RemoteOutpoint,
7✔
1329
                        witType,
7✔
1330
                        // No second level script as this is a commitment
7✔
1331
                        // output.
7✔
1332
                        nil,
7✔
1333
                        breachInfo.RemoteOutputSignDesc,
7✔
1334
                        breachInfo.BreachHeight,
7✔
1335
                        breachInfo.RemoteResolutionBlob,
7✔
1336
                )
7✔
1337

7✔
1338
                breachedOutputs = append(breachedOutputs, remoteOutput)
7✔
1339
        }
1340

1341
        // Lastly, for each of the breached HTLC outputs, record each as a
1342
        // breached output with the appropriate witness type based on its
1343
        // directionality. All HTLC outputs provided by the wallet are assumed
1344
        // to be non-dust.
1345
        for i, breachedHtlc := range breachInfo.HtlcRetributions {
17✔
1346
                // Using the breachedHtlc's incoming flag, determine the
7✔
1347
                // appropriate witness type that needs to be generated in order
7✔
1348
                // to sweep the HTLC output.
7✔
1349
                var htlcWitnessType input.StandardWitnessType
7✔
1350
                switch {
7✔
1351
                case isTaproot && breachedHtlc.IsIncoming:
×
1352
                        htlcWitnessType = input.TaprootHtlcAcceptedRevoke
×
1353

1354
                case isTaproot && !breachedHtlc.IsIncoming:
×
1355
                        htlcWitnessType = input.TaprootHtlcOfferedRevoke
×
1356

1357
                case !isTaproot && breachedHtlc.IsIncoming:
×
1358
                        htlcWitnessType = input.HtlcAcceptedRevoke
×
1359

1360
                case !isTaproot && !breachedHtlc.IsIncoming:
7✔
1361
                        htlcWitnessType = input.HtlcOfferedRevoke
7✔
1362
                }
1363

1364
                htlcOutput := makeBreachedOutput(
7✔
1365
                        &breachInfo.HtlcRetributions[i].OutPoint,
7✔
1366
                        htlcWitnessType,
7✔
1367
                        breachInfo.HtlcRetributions[i].SecondLevelWitnessScript,
7✔
1368
                        &breachInfo.HtlcRetributions[i].SignDesc,
7✔
1369
                        breachInfo.BreachHeight,
7✔
1370
                        breachInfo.HtlcRetributions[i].ResolutionBlob,
7✔
1371
                )
7✔
1372

7✔
1373
                // For taproot outputs, we also need to hold onto the second
7✔
1374
                // level tap tweak as well.
7✔
1375
                //nolint:ll
7✔
1376
                htlcOutput.secondLevelTapTweak = breachedHtlc.SecondLevelTapTweak
7✔
1377

7✔
1378
                breachedOutputs = append(breachedOutputs, htlcOutput)
7✔
1379
        }
1380

1381
        return &retributionInfo{
10✔
1382
                commitHash:      breachInfo.BreachTxHash,
10✔
1383
                chainHash:       breachInfo.ChainHash,
10✔
1384
                chanPoint:       *chanPoint,
10✔
1385
                breachedOutputs: breachedOutputs,
10✔
1386
                breachHeight:    breachInfo.BreachHeight,
10✔
1387
        }
10✔
1388
}
1389

1390
// justiceTxVariants is a struct that holds transactions which exacts "justice"
1391
// by sweeping ALL the funds within the channel which we are now entitled to
1392
// due to a breach of the channel's contract by the counterparty. There are
1393
// four variants of justice transactions:
1394
//
1395
// 1. The "normal" justice tx that spends all breached outputs.
1396
// 2. A tx that spends only the breached to_local output and to_remote output
1397
// (can be nil if none of these exist).
1398
// 3. A tx that spends all the breached commitment level HTLC outputs (can be
1399
// nil if none of these exist or if all have been taken to the second level).
1400
// 4. A set of txs that spend all the second-level HTLC outputs (can be empty if
1401
// no HTLC second-level txs have been confirmed).
1402
//
1403
// The reason we create these three variants, is that in certain cases (like
1404
// with the anchor output HTLC malleability), the channel counter party can pin
1405
// the HTLC outputs with low fee children, hindering our normal justice tx that
1406
// attempts to spend these outputs from propagating. In this case we want to
1407
// spend the to_local output and commitment level HTLC outputs separately,
1408
// before the CSV locks expire.
1409
type justiceTxVariants struct {
1410
        spendAll              *justiceTxCtx
1411
        spendCommitOuts       *justiceTxCtx
1412
        spendHTLCs            *justiceTxCtx
1413
        spendSecondLevelHTLCs []*justiceTxCtx
1414
}
1415

1416
// createJusticeTx creates transactions which exacts "justice" by sweeping ALL
1417
// the funds within the channel which we are now entitled to due to a breach of
1418
// the channel's contract by the counterparty. This function returns a *fully*
1419
// signed transaction with the witness for each input fully in place.
1420
func (b *BreachArbitrator) createJusticeTx(
1421
        breachedOutputs []breachedOutput) (*justiceTxVariants, error) {
14✔
1422

14✔
1423
        var (
14✔
1424
                allInputs         []input.Input
14✔
1425
                commitInputs      []input.Input
14✔
1426
                htlcInputs        []input.Input
14✔
1427
                secondLevelInputs []input.Input
14✔
1428
        )
14✔
1429

14✔
1430
        for i := range breachedOutputs {
48✔
1431
                // Grab locally scoped reference to breached output.
34✔
1432
                inp := &breachedOutputs[i]
34✔
1433
                allInputs = append(allInputs, inp)
34✔
1434

34✔
1435
                // Check if the input is from a commitment output, a commitment
34✔
1436
                // level HTLC output or a second level HTLC output.
34✔
1437
                switch inp.WitnessType() {
34✔
1438
                case input.HtlcAcceptedRevoke, input.HtlcOfferedRevoke,
1439
                        input.TaprootHtlcAcceptedRevoke,
1440
                        input.TaprootHtlcOfferedRevoke:
8✔
1441

8✔
1442
                        htlcInputs = append(htlcInputs, inp)
8✔
1443

1444
                case input.HtlcSecondLevelRevoke,
1445
                        input.TaprootHtlcSecondLevelRevoke:
4✔
1446

4✔
1447
                        secondLevelInputs = append(secondLevelInputs, inp)
4✔
1448

1449
                default:
22✔
1450
                        commitInputs = append(commitInputs, inp)
22✔
1451
                }
1452
        }
1453

1454
        var (
14✔
1455
                txs = &justiceTxVariants{}
14✔
1456
                err error
14✔
1457
        )
14✔
1458

14✔
1459
        // For each group of inputs, create a tx that spends them.
14✔
1460
        txs.spendAll, err = b.createSweepTx(allInputs...)
14✔
1461
        if err != nil {
14✔
1462
                return nil, err
×
1463
        }
×
1464

1465
        txs.spendCommitOuts, err = b.createSweepTx(commitInputs...)
14✔
1466
        if err != nil {
14✔
1467
                brarLog.Errorf("could not create sweep tx for commitment "+
×
1468
                        "outputs: %v", err)
×
1469
        }
×
1470

1471
        txs.spendHTLCs, err = b.createSweepTx(htlcInputs...)
14✔
1472
        if err != nil {
14✔
1473
                brarLog.Errorf("could not create sweep tx for HTLC outputs: %v",
×
1474
                        err)
×
1475
        }
×
1476

1477
        // TODO(roasbeef): only register one of them?
1478

1479
        secondLevelSweeps := make([]*justiceTxCtx, 0, len(secondLevelInputs))
14✔
1480
        for _, input := range secondLevelInputs {
18✔
1481
                sweepTx, err := b.createSweepTx(input)
4✔
1482
                if err != nil {
4✔
1483
                        brarLog.Errorf("could not create sweep tx for "+
×
1484
                                "second-level HTLC output: %v", err)
×
1485

×
1486
                        continue
×
1487
                }
1488

1489
                secondLevelSweeps = append(secondLevelSweeps, sweepTx)
4✔
1490
        }
1491
        txs.spendSecondLevelHTLCs = secondLevelSweeps
14✔
1492

14✔
1493
        return txs, nil
14✔
1494
}
1495

1496
// justiceTxCtx contains the justice transaction along with other related meta
1497
// data.
1498
type justiceTxCtx struct {
1499
        justiceTx *wire.MsgTx
1500

1501
        sweepAddr lnwallet.AddrWithKey
1502

1503
        extraTxOut fn.Option[sweep.SweepOutput]
1504

1505
        fee btcutil.Amount
1506

1507
        inputs []input.Input
1508
}
1509

1510
// createSweepTx creates a tx that sweeps the passed inputs back to our wallet.
1511
func (b *BreachArbitrator) createSweepTx(
1512
        inputs ...input.Input) (*justiceTxCtx, error) {
46✔
1513

46✔
1514
        if len(inputs) == 0 {
55✔
1515
                return nil, nil
9✔
1516
        }
9✔
1517

1518
        // We will assemble the breached outputs into a slice of spendable
1519
        // outputs, while simultaneously computing the estimated weight of the
1520
        // transaction.
1521
        var (
37✔
1522
                spendableOutputs []input.Input
37✔
1523
                weightEstimate   input.TxWeightEstimator
37✔
1524
        )
37✔
1525

37✔
1526
        // Allocate enough space to potentially hold each of the breached
37✔
1527
        // outputs in the retribution info.
37✔
1528
        spendableOutputs = make([]input.Input, 0, len(inputs))
37✔
1529

37✔
1530
        // The justice transaction we construct will be a segwit transaction
37✔
1531
        // that pays to a p2tr output. Components such as the version,
37✔
1532
        // nLockTime, and output are already included in the TxWeightEstimator.
37✔
1533
        weightEstimate.AddP2TROutput()
37✔
1534

37✔
1535
        // If any of our inputs has a resolution blob, then we'll add another
37✔
1536
        // P2TR _output_, since we'll want to separate the custom channel
37✔
1537
        // outputs from the regular, BTC only outputs. So we only need one such
37✔
1538
        // output, which'll carry the custom channel "valuables" from both the
37✔
1539
        // breached commitment and HTLC outputs.
37✔
1540
        hasBlobs := fn.Any(inputs, func(i input.Input) bool {
74✔
1541
                return i.ResolutionBlob().IsSome()
37✔
1542
        })
37✔
1543
        if hasBlobs {
63✔
1544
                weightEstimate.AddP2TROutput()
26✔
1545
        }
26✔
1546

1547
        // Next, we iterate over the breached outputs contained in the
1548
        // retribution info.  For each, we switch over the witness type such
1549
        // that we contribute the appropriate weight for each input and
1550
        // witness, finally adding to our list of spendable outputs.
1551
        for i := range inputs {
105✔
1552
                // Grab locally scoped reference to breached output.
68✔
1553
                inp := inputs[i]
68✔
1554

68✔
1555
                // First, determine the appropriate estimated witness weight
68✔
1556
                // for the give witness type of this breached output. If the
68✔
1557
                // witness weight cannot be estimated, we will omit it from the
68✔
1558
                // transaction.
68✔
1559
                witnessWeight, _, err := inp.WitnessType().SizeUpperBound()
68✔
1560
                if err != nil {
68✔
1561
                        brarLog.Warnf("could not determine witness weight "+
×
1562
                                "for breached output in retribution info: %v",
×
1563
                                err)
×
1564
                        continue
×
1565
                }
1566
                weightEstimate.AddWitnessInput(witnessWeight)
68✔
1567

68✔
1568
                // Finally, append this input to our list of spendable outputs.
68✔
1569
                spendableOutputs = append(spendableOutputs, inp)
68✔
1570
        }
1571

1572
        txWeight := weightEstimate.Weight()
37✔
1573

37✔
1574
        return b.sweepSpendableOutputsTxn(txWeight, spendableOutputs...)
37✔
1575
}
1576

1577
// sweepSpendableOutputsTxn creates a signed transaction from a sequence of
1578
// spendable outputs by sweeping the funds into a single p2wkh output.
1579
func (b *BreachArbitrator) sweepSpendableOutputsTxn(txWeight lntypes.WeightUnit,
1580
        inputs ...input.Input) (*justiceTxCtx, error) {
37✔
1581

37✔
1582
        // First, we obtain a new public key script from the wallet which we'll
37✔
1583
        // sweep the funds to.
37✔
1584
        // TODO(roasbeef): possibly create many outputs to minimize change in
37✔
1585
        // the future?
37✔
1586
        pkScript, err := b.cfg.GenSweepScript().Unpack()
37✔
1587
        if err != nil {
37✔
1588
                return nil, err
×
1589
        }
×
1590

1591
        // Compute the total amount contained in the inputs.
1592
        var totalAmt btcutil.Amount
37✔
1593
        for _, inp := range inputs {
105✔
1594
                totalAmt += btcutil.Amount(inp.SignDesc().Output.Value)
68✔
1595
        }
68✔
1596

1597
        // We'll actually attempt to target inclusion within the next two
1598
        // blocks as we'd like to sweep these funds back into our wallet ASAP.
1599
        feePerKw, err := b.cfg.Estimator.EstimateFeePerKW(justiceTxConfTarget)
37✔
1600
        if err != nil {
37✔
1601
                return nil, err
×
1602
        }
×
1603
        txFee := feePerKw.FeeForWeight(txWeight)
37✔
1604

37✔
1605
        // At this point, we'll check to see if we have any extra outputs to
37✔
1606
        // add from the aux sweeper.
37✔
1607
        extraChangeOut := fn.MapOptionZ(
37✔
1608
                b.cfg.AuxSweeper,
37✔
1609
                func(aux sweep.AuxSweeper) fn.Result[sweep.SweepOutput] {
37✔
1610
                        return aux.DeriveSweepAddr(inputs, pkScript)
×
1611
                },
×
1612
        )
1613
        if err := extraChangeOut.Err(); err != nil {
37✔
1614
                return nil, err
×
1615
        }
×
1616

1617
        // TODO(roasbeef): already start to siphon their funds into fees
1618
        sweepAmt := int64(totalAmt - txFee)
37✔
1619

37✔
1620
        // With the fee calculated, we can now create the transaction using the
37✔
1621
        // information gathered above and the provided retribution information.
37✔
1622
        txn := wire.NewMsgTx(2)
37✔
1623

37✔
1624
        // First, we'll add the extra sweep output if it exists, subtracting the
37✔
1625
        // amount from the sweep amt.
37✔
1626
        if b.cfg.AuxSweeper.IsSome() {
37✔
1627
                extraChangeOut.WhenOk(func(o sweep.SweepOutput) {
×
1628
                        sweepAmt -= o.Value
×
1629

×
1630
                        txn.AddTxOut(&o.TxOut)
×
1631
                })
×
1632
        }
1633

1634
        // Next, we'll add the output to which our funds will be deposited.
1635
        txn.AddTxOut(&wire.TxOut{
37✔
1636
                PkScript: pkScript.DeliveryAddress,
37✔
1637
                Value:    sweepAmt,
37✔
1638
        })
37✔
1639

37✔
1640
        // TODO(roasbeef): add other output change modify sweep amt
37✔
1641

37✔
1642
        // Next, we add all of the spendable outputs as inputs to the
37✔
1643
        // transaction.
37✔
1644
        for _, inp := range inputs {
105✔
1645
                txn.AddTxIn(&wire.TxIn{
68✔
1646
                        PreviousOutPoint: inp.OutPoint(),
68✔
1647
                        Sequence:         inp.BlocksToMaturity(),
68✔
1648
                })
68✔
1649
        }
68✔
1650

1651
        // Before signing the transaction, check to ensure that it meets some
1652
        // basic validity requirements.
1653
        btx := btcutil.NewTx(txn)
37✔
1654
        if err := blockchain.CheckTransactionSanity(btx); err != nil {
37✔
1655
                return nil, err
×
1656
        }
×
1657

1658
        // Create a sighash cache to improve the performance of hashing and
1659
        // signing SigHashAll inputs.
1660
        prevOutputFetcher, err := input.MultiPrevOutFetcher(inputs)
37✔
1661
        if err != nil {
37✔
1662
                return nil, err
×
1663
        }
×
1664
        hashCache := txscript.NewTxSigHashes(txn, prevOutputFetcher)
37✔
1665

37✔
1666
        // Create a closure that encapsulates the process of initializing a
37✔
1667
        // particular output's witness generation function, computing the
37✔
1668
        // witness, and attaching it to the transaction. This function accepts
37✔
1669
        // an integer index representing the intended txin index, and the
37✔
1670
        // breached output from which it will spend.
37✔
1671
        addWitness := func(idx int, so input.Input) error {
105✔
1672
                // First, we construct a valid witness for this outpoint and
68✔
1673
                // transaction using the SpendableOutput's witness generation
68✔
1674
                // function.
68✔
1675
                inputScript, err := so.CraftInputScript(
68✔
1676
                        b.cfg.Signer, txn, hashCache, prevOutputFetcher, idx,
68✔
1677
                )
68✔
1678
                if err != nil {
68✔
1679
                        return err
×
1680
                }
×
1681

1682
                // Then, we add the witness to the transaction at the
1683
                // appropriate txin index.
1684
                txn.TxIn[idx].Witness = inputScript.Witness
68✔
1685

68✔
1686
                return nil
68✔
1687
        }
1688

1689
        // Finally, generate a witness for each output and attach it to the
1690
        // transaction.
1691
        for i, inp := range inputs {
105✔
1692
                if err := addWitness(i, inp); err != nil {
68✔
1693
                        return nil, err
×
1694
                }
×
1695
        }
1696

1697
        return &justiceTxCtx{
37✔
1698
                justiceTx:  txn,
37✔
1699
                sweepAddr:  pkScript,
37✔
1700
                extraTxOut: extraChangeOut.OkToSome(),
37✔
1701
                fee:        txFee,
37✔
1702
                inputs:     inputs,
37✔
1703
        }, nil
37✔
1704
}
1705

1706
// RetributionStore handles persistence of retribution states to disk and is
1707
// backed by a boltdb bucket. The primary responsibility of the retribution
1708
// store is to ensure that we can recover from a restart in the middle of a
1709
// breached contract retribution.
1710
type RetributionStore struct {
1711
        db kvdb.Backend
1712
}
1713

1714
// NewRetributionStore creates a new instance of a RetributionStore.
1715
func NewRetributionStore(db kvdb.Backend) *RetributionStore {
19✔
1716
        return &RetributionStore{
19✔
1717
                db: db,
19✔
1718
        }
19✔
1719
}
19✔
1720

1721
// taprootBriefcaseFromRetInfo creates a taprootBriefcase from a retribution
1722
// info struct. This stores all the tap tweak informatoin we need to inrder to
1723
// be able to hadnel breaches after a restart.
1724
func taprootBriefcaseFromRetInfo(retInfo *retributionInfo) *taprootBriefcase {
×
1725
        tapCase := newTaprootBriefcase()
×
1726

×
1727
        for _, bo := range retInfo.breachedOutputs {
×
1728
                switch bo.WitnessType() {
×
1729
                // For spending from our commitment output on the remote
1730
                // commitment, we'll need to stash the control block.
1731
                case input.TaprootRemoteCommitSpend:
×
1732
                        //nolint:ll
×
1733
                        tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock = bo.signDesc.ControlBlock
×
1734

×
1735
                        bo.resolutionBlob.WhenSome(func(blob tlv.Blob) {
×
1736
                                tapCase.SettledCommitBlob = tlv.SomeRecordT(
×
1737
                                        tlv.NewPrimitiveRecord[tlv.TlvType2](
×
1738
                                                blob,
×
1739
                                        ),
×
1740
                                )
×
1741
                        })
×
1742

1743
                // To spend the revoked output again, we'll store the same
1744
                // control block value as above, but in a different place.
1745
                case input.TaprootCommitmentRevoke:
×
1746
                        //nolint:ll
×
1747
                        tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock = bo.signDesc.ControlBlock
×
1748

×
1749
                        bo.resolutionBlob.WhenSome(func(blob tlv.Blob) {
×
1750
                                tapCase.BreachedCommitBlob = tlv.SomeRecordT(
×
1751
                                        tlv.NewPrimitiveRecord[tlv.TlvType3](
×
1752
                                                blob,
×
1753
                                        ),
×
1754
                                )
×
1755
                        })
×
1756

1757
                // For spending the HTLC outputs, we'll store the first and
1758
                // second level tweak values.
1759
                case input.TaprootHtlcAcceptedRevoke:
×
1760
                        fallthrough
×
1761
                case input.TaprootHtlcOfferedRevoke:
×
1762
                        resID := newResolverID(bo.OutPoint())
×
1763

×
1764
                        var firstLevelTweak [32]byte
×
1765
                        copy(firstLevelTweak[:], bo.signDesc.TapTweak)
×
1766
                        secondLevelTweak := bo.secondLevelTapTweak
×
1767

×
1768
                        //nolint:ll
×
1769
                        tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID] = firstLevelTweak
×
1770

×
1771
                        //nolint:ll
×
1772
                        tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID] = secondLevelTweak
×
1773
                }
1774
        }
1775

1776
        return tapCase
×
1777
}
1778

1779
// applyTaprootRetInfo attaches the taproot specific inforamtion in the tapCase
1780
// to the passed retInfo struct.
1781
func applyTaprootRetInfo(tapCase *taprootBriefcase,
1782
        retInfo *retributionInfo) error {
×
1783

×
1784
        for i := range retInfo.breachedOutputs {
×
1785
                bo := retInfo.breachedOutputs[i]
×
1786

×
1787
                switch bo.WitnessType() {
×
1788
                // For spending from our commitment output on the remote
1789
                // commitment, we'll apply the control block.
1790
                case input.TaprootRemoteCommitSpend:
×
1791
                        //nolint:ll
×
1792
                        bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.CommitSweepCtrlBlock
×
1793

×
1794
                        tapCase.SettledCommitBlob.WhenSomeV(
×
1795
                                func(blob tlv.Blob) {
×
1796
                                        bo.resolutionBlob = fn.Some(blob)
×
1797
                                },
×
1798
                        )
1799

1800
                // To spend the revoked output again, we'll apply the same
1801
                // control block value as above, but to a different place.
1802
                case input.TaprootCommitmentRevoke:
×
1803
                        //nolint:ll
×
1804
                        bo.signDesc.ControlBlock = tapCase.CtrlBlocks.Val.RevokeSweepCtrlBlock
×
1805

×
1806
                        tapCase.BreachedCommitBlob.WhenSomeV(
×
1807
                                func(blob tlv.Blob) {
×
1808
                                        bo.resolutionBlob = fn.Some(blob)
×
1809
                                },
×
1810
                        )
1811

1812
                // For spending the HTLC outputs, we'll apply the first and
1813
                // second level tweak values.
1814
                case input.TaprootHtlcAcceptedRevoke:
×
1815
                        fallthrough
×
1816
                case input.TaprootHtlcOfferedRevoke:
×
1817
                        resID := newResolverID(bo.OutPoint())
×
1818

×
1819
                        //nolint:ll
×
1820
                        tap1, ok := tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID]
×
1821
                        if !ok {
×
1822
                                return fmt.Errorf("unable to find taproot "+
×
1823
                                        "tweak for: %v", bo.OutPoint())
×
1824
                        }
×
1825
                        bo.signDesc.TapTweak = tap1[:]
×
1826

×
1827
                        //nolint:ll
×
1828
                        tap2, ok := tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID]
×
1829
                        if !ok {
×
1830
                                return fmt.Errorf("unable to find taproot "+
×
1831
                                        "tweak for: %v", bo.OutPoint())
×
1832
                        }
×
1833
                        bo.secondLevelTapTweak = tap2
×
1834
                }
1835

1836
                retInfo.breachedOutputs[i] = bo
×
1837
        }
1838

1839
        return nil
×
1840
}
1841

1842
// Add adds a retribution state to the RetributionStore, which is then persisted
1843
// to disk.
1844
func (rs *RetributionStore) Add(ret *retributionInfo) error {
14✔
1845
        return kvdb.Update(rs.db, func(tx kvdb.RwTx) error {
28✔
1846
                // If this is our first contract breach, the retributionBucket
14✔
1847
                // won't exist, in which case, we just create a new bucket.
14✔
1848
                retBucket, err := tx.CreateTopLevelBucket(retributionBucket)
14✔
1849
                if err != nil {
14✔
1850
                        return err
×
1851
                }
×
1852
                tapRetBucket, err := tx.CreateTopLevelBucket(
14✔
1853
                        taprootRetributionBucket,
14✔
1854
                )
14✔
1855
                if err != nil {
14✔
1856
                        return err
×
1857
                }
×
1858

1859
                var outBuf bytes.Buffer
14✔
1860
                err = graphdb.WriteOutpoint(&outBuf, &ret.chanPoint)
14✔
1861
                if err != nil {
14✔
1862
                        return err
×
1863
                }
×
1864

1865
                var retBuf bytes.Buffer
14✔
1866
                if err := ret.Encode(&retBuf); err != nil {
14✔
1867
                        return err
×
1868
                }
×
1869

1870
                err = retBucket.Put(outBuf.Bytes(), retBuf.Bytes())
14✔
1871
                if err != nil {
14✔
1872
                        return err
×
1873
                }
×
1874

1875
                // If this isn't a taproot channel, then we can exit early here
1876
                // as there's no extra data to write.
1877
                switch {
14✔
1878
                case len(ret.breachedOutputs) == 0:
×
1879
                        return nil
×
1880

1881
                case !txscript.IsPayToTaproot(
1882
                        ret.breachedOutputs[0].signDesc.Output.PkScript,
1883
                ):
14✔
1884
                        return nil
14✔
1885
                }
1886

1887
                // We'll also map the ret info into the taproot storage
1888
                // structure we need for taproot channels.
1889
                var b bytes.Buffer
×
1890
                tapRetcase := taprootBriefcaseFromRetInfo(ret)
×
1891
                if err := tapRetcase.Encode(&b); err != nil {
×
1892
                        return err
×
1893
                }
×
1894

1895
                return tapRetBucket.Put(outBuf.Bytes(), b.Bytes())
×
1896
        }, func() {})
14✔
1897
}
1898

1899
// IsBreached queries the retribution store to discern if this channel was
1900
// previously breached. This is used when connecting to a peer to determine if
1901
// it is safe to add a link to the htlcswitch, as we should never add a channel
1902
// that has already been breached.
1903
func (rs *RetributionStore) IsBreached(chanPoint *wire.OutPoint) (bool, error) {
20✔
1904
        var found bool
20✔
1905
        err := kvdb.View(rs.db, func(tx kvdb.RTx) error {
40✔
1906
                retBucket := tx.ReadBucket(retributionBucket)
20✔
1907
                if retBucket == nil {
28✔
1908
                        return nil
8✔
1909
                }
8✔
1910

1911
                var chanBuf bytes.Buffer
12✔
1912
                err := graphdb.WriteOutpoint(&chanBuf, chanPoint)
12✔
1913
                if err != nil {
12✔
1914
                        return err
×
1915
                }
×
1916

1917
                retInfo := retBucket.Get(chanBuf.Bytes())
12✔
1918
                if retInfo != nil {
20✔
1919
                        found = true
8✔
1920
                }
8✔
1921

1922
                return nil
12✔
1923
        }, func() {
20✔
1924
                found = false
20✔
1925
        })
20✔
1926

1927
        return found, err
20✔
1928
}
1929

1930
// Remove removes a retribution state and finalized justice transaction by
1931
// channel point  from the retribution store.
1932
func (rs *RetributionStore) Remove(chanPoint *wire.OutPoint) error {
10✔
1933
        return kvdb.Update(rs.db, func(tx kvdb.RwTx) error {
20✔
1934
                retBucket := tx.ReadWriteBucket(retributionBucket)
10✔
1935
                tapRetBucket, err := tx.CreateTopLevelBucket(
10✔
1936
                        taprootRetributionBucket,
10✔
1937
                )
10✔
1938
                if err != nil {
10✔
1939
                        return err
×
1940
                }
×
1941

1942
                // We return an error if the bucket is not already created,
1943
                // since normal operation of the breach arbiter should never
1944
                // try to remove a finalized retribution state that is not
1945
                // already stored in the db.
1946
                if retBucket == nil {
12✔
1947
                        return errors.New("unable to remove retribution " +
2✔
1948
                                "because the retribution bucket doesn't exist")
2✔
1949
                }
2✔
1950

1951
                // Serialize the channel point we are intending to remove.
1952
                var chanBuf bytes.Buffer
8✔
1953
                err = graphdb.WriteOutpoint(&chanBuf, chanPoint)
8✔
1954
                if err != nil {
8✔
1955
                        return err
×
1956
                }
×
1957
                chanBytes := chanBuf.Bytes()
8✔
1958

8✔
1959
                // Remove the persisted retribution info and finalized justice
8✔
1960
                // transaction.
8✔
1961
                if err := retBucket.Delete(chanBytes); err != nil {
8✔
1962
                        return err
×
1963
                }
×
1964

1965
                return tapRetBucket.Delete(chanBytes)
8✔
1966
        }, func() {})
10✔
1967
}
1968

1969
// ForAll iterates through all stored retributions and executes the passed
1970
// callback function on each retribution.
1971
func (rs *RetributionStore) ForAll(cb func(*retributionInfo) error,
1972
        reset func()) error {
46✔
1973

46✔
1974
        return kvdb.View(rs.db, func(tx kvdb.RTx) error {
92✔
1975
                // If the bucket does not exist, then there are no pending
46✔
1976
                // retributions.
46✔
1977
                retBucket := tx.ReadBucket(retributionBucket)
46✔
1978
                if retBucket == nil {
64✔
1979
                        return nil
18✔
1980
                }
18✔
1981
                tapRetBucket := tx.ReadBucket(
28✔
1982
                        taprootRetributionBucket,
28✔
1983
                )
28✔
1984

28✔
1985
                // Otherwise, we fetch each serialized retribution info,
28✔
1986
                // deserialize it, and execute the passed in callback function
28✔
1987
                // on it.
28✔
1988
                return retBucket.ForEach(func(k, retBytes []byte) error {
66✔
1989
                        ret := &retributionInfo{}
38✔
1990
                        err := ret.Decode(bytes.NewBuffer(retBytes))
38✔
1991
                        if err != nil {
38✔
1992
                                return err
×
1993
                        }
×
1994

1995
                        tapInfoBytes := tapRetBucket.Get(k)
38✔
1996
                        if tapInfoBytes != nil {
38✔
1997
                                var tapCase taprootBriefcase
×
1998
                                err := tapCase.Decode(
×
1999
                                        bytes.NewReader(tapInfoBytes),
×
2000
                                )
×
2001
                                if err != nil {
×
2002
                                        return err
×
2003
                                }
×
2004

2005
                                err = applyTaprootRetInfo(&tapCase, ret)
×
2006
                                if err != nil {
×
2007
                                        return err
×
2008
                                }
×
2009
                        }
2010

2011
                        return cb(ret)
38✔
2012
                })
2013
        }, reset)
2014
}
2015

2016
// Encode serializes the retribution into the passed byte stream.
2017
func (ret *retributionInfo) Encode(w io.Writer) error {
16✔
2018
        var scratch [4]byte
16✔
2019

16✔
2020
        if _, err := w.Write(ret.commitHash[:]); err != nil {
16✔
2021
                return err
×
2022
        }
×
2023

2024
        if err := graphdb.WriteOutpoint(w, &ret.chanPoint); err != nil {
16✔
2025
                return err
×
2026
        }
×
2027

2028
        if _, err := w.Write(ret.chainHash[:]); err != nil {
16✔
2029
                return err
×
2030
        }
×
2031

2032
        binary.BigEndian.PutUint32(scratch[:], ret.breachHeight)
16✔
2033
        if _, err := w.Write(scratch[:]); err != nil {
16✔
2034
                return err
×
2035
        }
×
2036

2037
        nOutputs := len(ret.breachedOutputs)
16✔
2038
        if err := wire.WriteVarInt(w, 0, uint64(nOutputs)); err != nil {
16✔
2039
                return err
×
2040
        }
×
2041

2042
        for _, output := range ret.breachedOutputs {
50✔
2043
                if err := output.Encode(w); err != nil {
34✔
2044
                        return err
×
2045
                }
×
2046
        }
2047

2048
        return nil
16✔
2049
}
2050

2051
// Decode deserializes a retribution from the passed byte stream.
2052
func (ret *retributionInfo) Decode(r io.Reader) error {
40✔
2053
        var scratch [32]byte
40✔
2054

40✔
2055
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
40✔
2056
                return err
×
2057
        }
×
2058
        hash, err := chainhash.NewHash(scratch[:])
40✔
2059
        if err != nil {
40✔
2060
                return err
×
2061
        }
×
2062
        ret.commitHash = *hash
40✔
2063

40✔
2064
        if err := graphdb.ReadOutpoint(r, &ret.chanPoint); err != nil {
40✔
2065
                return err
×
2066
        }
×
2067

2068
        if _, err := io.ReadFull(r, scratch[:]); err != nil {
40✔
2069
                return err
×
2070
        }
×
2071
        chainHash, err := chainhash.NewHash(scratch[:])
40✔
2072
        if err != nil {
40✔
2073
                return err
×
2074
        }
×
2075
        ret.chainHash = *chainHash
40✔
2076

40✔
2077
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
40✔
2078
                return err
×
2079
        }
×
2080
        ret.breachHeight = binary.BigEndian.Uint32(scratch[:4])
40✔
2081

40✔
2082
        nOutputsU64, err := wire.ReadVarInt(r, 0)
40✔
2083
        if err != nil {
40✔
2084
                return err
×
2085
        }
×
2086
        nOutputs := int(nOutputsU64)
40✔
2087

40✔
2088
        ret.breachedOutputs = make([]breachedOutput, nOutputs)
40✔
2089
        for i := range ret.breachedOutputs {
120✔
2090
                if err := ret.breachedOutputs[i].Decode(r); err != nil {
80✔
2091
                        return err
×
2092
                }
×
2093
        }
2094

2095
        return nil
40✔
2096
}
2097

2098
// Encode serializes a breachedOutput into the passed byte stream.
2099
func (bo *breachedOutput) Encode(w io.Writer) error {
38✔
2100
        var scratch [8]byte
38✔
2101

38✔
2102
        binary.BigEndian.PutUint64(scratch[:8], uint64(bo.amt))
38✔
2103
        if _, err := w.Write(scratch[:8]); err != nil {
38✔
2104
                return err
×
2105
        }
×
2106

2107
        if err := graphdb.WriteOutpoint(w, &bo.outpoint); err != nil {
38✔
2108
                return err
×
2109
        }
×
2110

2111
        err := input.WriteSignDescriptor(w, &bo.signDesc)
38✔
2112
        if err != nil {
38✔
2113
                return err
×
2114
        }
×
2115

2116
        err = wire.WriteVarBytes(w, 0, bo.secondLevelWitnessScript)
38✔
2117
        if err != nil {
38✔
2118
                return err
×
2119
        }
×
2120

2121
        binary.BigEndian.PutUint16(scratch[:2], uint16(bo.witnessType))
38✔
2122
        if _, err := w.Write(scratch[:2]); err != nil {
38✔
2123
                return err
×
2124
        }
×
2125

2126
        return nil
38✔
2127
}
2128

2129
// Decode deserializes a breachedOutput from the passed byte stream.
2130
func (bo *breachedOutput) Decode(r io.Reader) error {
84✔
2131
        var scratch [8]byte
84✔
2132

84✔
2133
        if _, err := io.ReadFull(r, scratch[:8]); err != nil {
84✔
2134
                return err
×
2135
        }
×
2136
        bo.amt = btcutil.Amount(binary.BigEndian.Uint64(scratch[:8]))
84✔
2137

84✔
2138
        if err := graphdb.ReadOutpoint(r, &bo.outpoint); err != nil {
84✔
2139
                return err
×
2140
        }
×
2141

2142
        if err := input.ReadSignDescriptor(r, &bo.signDesc); err != nil {
84✔
2143
                return err
×
2144
        }
×
2145

2146
        wScript, err := wire.ReadVarBytes(r, 0, 1000, "witness script")
84✔
2147
        if err != nil {
84✔
2148
                return err
×
2149
        }
×
2150
        bo.secondLevelWitnessScript = wScript
84✔
2151

84✔
2152
        if _, err := io.ReadFull(r, scratch[:2]); err != nil {
84✔
2153
                return err
×
2154
        }
×
2155
        bo.witnessType = input.StandardWitnessType(
84✔
2156
                binary.BigEndian.Uint16(scratch[:2]),
84✔
2157
        )
84✔
2158

84✔
2159
        return nil
84✔
2160
}
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