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

lightningnetwork / lnd / 12058234999

27 Nov 2024 09:06PM UTC coverage: 57.847% (-1.1%) from 58.921%
12058234999

Pull #9148

github

ProofOfKeags
lnwire: convert DynPropose and DynCommit to use typed tlv records
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

142 of 177 new or added lines in 4 files covered. (80.23%)

19365 existing lines in 251 files now uncovered.

100876 of 174383 relevant lines covered (57.85%)

25338.28 hits per line

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

69.28
/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"
19
        "github.com/lightningnetwork/lnd/input"
20
        "github.com/lightningnetwork/lnd/kvdb"
21
        "github.com/lightningnetwork/lnd/labels"
22
        "github.com/lightningnetwork/lnd/lntypes"
23
        "github.com/lightningnetwork/lnd/lnutils"
24
        "github.com/lightningnetwork/lnd/lnwallet"
25
        "github.com/lightningnetwork/lnd/lnwallet/chainfee"
26
        "github.com/lightningnetwork/lnd/sweep"
27
        "github.com/lightningnetwork/lnd/tlv"
28
)
29

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

196
        cfg *BreachConfig
197

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

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

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

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

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

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

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

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

×
UNCOV
265
                if chanSummary.IsPending {
×
UNCOV
266
                        continue
×
267
                }
268

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

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

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

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

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

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

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

8✔
315
        return nil
8✔
316
}
317

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

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

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

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

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

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

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

×
UNCOV
363
        return false, nil
×
364
}
365

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

535
                return spends, nil
13✔
536

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
646
                        continue
2✔
647
                }
648

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

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

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

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

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

681
                inputs[nextIndex] = inputs[i]
16✔
682
                nextIndex++
16✔
683
        }
684

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4✔
971
        return nil
4✔
972
}
973

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1100
        witnessFunc input.WitnessGenerator
1101

1102
        resolutionBlob fn.Option[tlv.Blob]
1103

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1238
        breachedOutputs []breachedOutput
1239
}
1240

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

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

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

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

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

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

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

10✔
1287
                        witnessType = input.CommitSpendNoDelayTweakless
10✔
1288

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

7✔
1441
                        htlcInputs = append(htlcInputs, inp)
7✔
1442

1443
                case input.HtlcSecondLevelRevoke,
1444
                        input.TaprootHtlcSecondLevelRevoke:
5✔
1445

5✔
1446
                        secondLevelInputs = append(secondLevelInputs, inp)
5✔
1447

1448
                default:
23✔
1449
                        commitInputs = append(commitInputs, inp)
23✔
1450
                }
1451
        }
1452

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

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

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

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

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

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

×
1485
                        continue
×
1486
                }
1487

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

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

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

1500
        sweepAddr lnwallet.AddrWithKey
1501

1502
        extraTxOut fn.Option[sweep.SweepOutput]
1503

1504
        fee btcutil.Amount
1505

1506
        inputs []input.Input
1507
}
1508

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

47✔
1513
        if len(inputs) == 0 {
57✔
1514
                return nil, nil
10✔
1515
        }
10✔
1516

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

70✔
1685
                return nil
70✔
1686
        }
1687

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

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

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

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

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

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

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

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

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

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

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

×
UNCOV
1767
                        //nolint:lll
×
UNCOV
1768
                        tapCase.TapTweaks.Val.BreachedHtlcTweaks[resID] = firstLevelTweak
×
UNCOV
1769

×
UNCOV
1770
                        //nolint:lll
×
UNCOV
1771
                        tapCase.TapTweaks.Val.BreachedSecondLevelHltcTweaks[resID] = secondLevelTweak
×
1772
                }
1773
        }
1774

UNCOV
1775
        return tapCase
×
1776
}
1777

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

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

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

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

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

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

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

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

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

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

UNCOV
1838
        return nil
×
1839
}
1840

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

1858
                var outBuf bytes.Buffer
14✔
1859
                if err := writeOutpoint(&outBuf, &ret.chanPoint); err != nil {
14✔
1860
                        return err
×
1861
                }
×
1862

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

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

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

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

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

UNCOV
1893
                return tapRetBucket.Put(outBuf.Bytes(), b.Bytes())
×
1894
        }, func() {})
14✔
1895
}
1896

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

1909
                var chanBuf bytes.Buffer
12✔
1910
                if err := writeOutpoint(&chanBuf, chanPoint); err != nil {
12✔
1911
                        return err
×
1912
                }
×
1913

1914
                retInfo := retBucket.Get(chanBuf.Bytes())
12✔
1915
                if retInfo != nil {
20✔
1916
                        found = true
8✔
1917
                }
8✔
1918

1919
                return nil
12✔
1920
        }, func() {
20✔
1921
                found = false
20✔
1922
        })
20✔
1923

1924
        return found, err
20✔
1925
}
1926

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

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

1948
                // Serialize the channel point we are intending to remove.
1949
                var chanBuf bytes.Buffer
8✔
1950
                if err := writeOutpoint(&chanBuf, chanPoint); err != nil {
8✔
1951
                        return err
×
1952
                }
×
1953
                chanBytes := chanBuf.Bytes()
8✔
1954

8✔
1955
                // Remove the persisted retribution info and finalized justice
8✔
1956
                // transaction.
8✔
1957
                if err := retBucket.Delete(chanBytes); err != nil {
8✔
1958
                        return err
×
1959
                }
×
1960

1961
                return tapRetBucket.Delete(chanBytes)
8✔
1962
        }, func() {})
10✔
1963
}
1964

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

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

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

1991
                        tapInfoBytes := tapRetBucket.Get(k)
38✔
1992
                        if tapInfoBytes != nil {
38✔
UNCOV
1993
                                var tapCase taprootBriefcase
×
UNCOV
1994
                                err := tapCase.Decode(
×
UNCOV
1995
                                        bytes.NewReader(tapInfoBytes),
×
UNCOV
1996
                                )
×
UNCOV
1997
                                if err != nil {
×
1998
                                        return err
×
1999
                                }
×
2000

UNCOV
2001
                                err = applyTaprootRetInfo(&tapCase, ret)
×
UNCOV
2002
                                if err != nil {
×
2003
                                        return err
×
2004
                                }
×
2005
                        }
2006

2007
                        return cb(ret)
38✔
2008
                })
2009
        }, reset)
2010
}
2011

2012
// Encode serializes the retribution into the passed byte stream.
2013
func (ret *retributionInfo) Encode(w io.Writer) error {
16✔
2014
        var scratch [4]byte
16✔
2015

16✔
2016
        if _, err := w.Write(ret.commitHash[:]); err != nil {
16✔
2017
                return err
×
2018
        }
×
2019

2020
        if err := writeOutpoint(w, &ret.chanPoint); err != nil {
16✔
2021
                return err
×
2022
        }
×
2023

2024
        if _, err := w.Write(ret.chainHash[:]); err != nil {
16✔
2025
                return err
×
2026
        }
×
2027

2028
        binary.BigEndian.PutUint32(scratch[:], ret.breachHeight)
16✔
2029
        if _, err := w.Write(scratch[:]); err != nil {
16✔
2030
                return err
×
2031
        }
×
2032

2033
        nOutputs := len(ret.breachedOutputs)
16✔
2034
        if err := wire.WriteVarInt(w, 0, uint64(nOutputs)); err != nil {
16✔
2035
                return err
×
2036
        }
×
2037

2038
        for _, output := range ret.breachedOutputs {
50✔
2039
                if err := output.Encode(w); err != nil {
34✔
2040
                        return err
×
2041
                }
×
2042
        }
2043

2044
        return nil
16✔
2045
}
2046

2047
// Decode deserializes a retribution from the passed byte stream.
2048
func (ret *retributionInfo) Decode(r io.Reader) error {
40✔
2049
        var scratch [32]byte
40✔
2050

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

40✔
2060
        if err := readOutpoint(r, &ret.chanPoint); err != nil {
40✔
2061
                return err
×
2062
        }
×
2063

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

40✔
2073
        if _, err := io.ReadFull(r, scratch[:4]); err != nil {
40✔
2074
                return err
×
2075
        }
×
2076
        ret.breachHeight = binary.BigEndian.Uint32(scratch[:4])
40✔
2077

40✔
2078
        nOutputsU64, err := wire.ReadVarInt(r, 0)
40✔
2079
        if err != nil {
40✔
2080
                return err
×
2081
        }
×
2082
        nOutputs := int(nOutputsU64)
40✔
2083

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

2091
        return nil
40✔
2092
}
2093

2094
// Encode serializes a breachedOutput into the passed byte stream.
2095
func (bo *breachedOutput) Encode(w io.Writer) error {
38✔
2096
        var scratch [8]byte
38✔
2097

38✔
2098
        binary.BigEndian.PutUint64(scratch[:8], uint64(bo.amt))
38✔
2099
        if _, err := w.Write(scratch[:8]); err != nil {
38✔
2100
                return err
×
2101
        }
×
2102

2103
        if err := writeOutpoint(w, &bo.outpoint); err != nil {
38✔
2104
                return err
×
2105
        }
×
2106

2107
        err := input.WriteSignDescriptor(w, &bo.signDesc)
38✔
2108
        if err != nil {
38✔
2109
                return err
×
2110
        }
×
2111

2112
        err = wire.WriteVarBytes(w, 0, bo.secondLevelWitnessScript)
38✔
2113
        if err != nil {
38✔
2114
                return err
×
2115
        }
×
2116

2117
        binary.BigEndian.PutUint16(scratch[:2], uint16(bo.witnessType))
38✔
2118
        if _, err := w.Write(scratch[:2]); err != nil {
38✔
2119
                return err
×
2120
        }
×
2121

2122
        return nil
38✔
2123
}
2124

2125
// Decode deserializes a breachedOutput from the passed byte stream.
2126
func (bo *breachedOutput) Decode(r io.Reader) error {
84✔
2127
        var scratch [8]byte
84✔
2128

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

84✔
2134
        if err := readOutpoint(r, &bo.outpoint); err != nil {
84✔
2135
                return err
×
2136
        }
×
2137

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

2142
        wScript, err := wire.ReadVarBytes(r, 0, 1000, "witness script")
84✔
2143
        if err != nil {
84✔
2144
                return err
×
2145
        }
×
2146
        bo.secondLevelWitnessScript = wScript
84✔
2147

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

84✔
2155
        return nil
84✔
2156
}
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