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

lightningnetwork / lnd / 13236757158

10 Feb 2025 08:39AM UTC coverage: 57.649% (-1.2%) from 58.815%
13236757158

Pull #9493

github

ziggie1984
lncli: for some cmds we don't replace the data of the response.

For some cmds it is not very practical to replace the json output
because we might pipe it into other commands. For example when
creating the route we want to pipe it into sendtoRoute.
Pull Request #9493: For some lncli cmds we should not replace the content with other data

0 of 9 new or added lines in 2 files covered. (0.0%)

19535 existing lines in 252 files now uncovered.

103517 of 179563 relevant lines covered (57.65%)

24878.49 hits per line

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

66.98
/contractcourt/commit_sweep_resolver.go
1
package contractcourt
2

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

10
        "github.com/btcsuite/btcd/btcutil"
11
        "github.com/btcsuite/btcd/chaincfg/chainhash"
12
        "github.com/btcsuite/btcd/txscript"
13
        "github.com/btcsuite/btcd/wire"
14
        "github.com/lightningnetwork/lnd/chainntnfs"
15
        "github.com/lightningnetwork/lnd/channeldb"
16
        "github.com/lightningnetwork/lnd/fn/v2"
17
        "github.com/lightningnetwork/lnd/input"
18
        "github.com/lightningnetwork/lnd/lnwallet"
19
        "github.com/lightningnetwork/lnd/sweep"
20
)
21

22
// commitSweepResolver is a resolver that will attempt to sweep the commitment
23
// output paying to us (local channel balance). In the case that the local
24
// party (we) broadcasts their version of the commitment transaction, we have
25
// to wait before sweeping it, as it has a CSV delay. For anchor channel
26
// type, even if the remote party broadcasts the commitment transaction,
27
// we have to wait one block after commitment transaction is confirmed,
28
// because CSV 1 is put into the script of UTXO representing local balance.
29
// Additionally, if the channel is a channel lease, we have to wait for
30
// CLTV to expire.
31
// https://docs.lightning.engineering/lightning-network-tools/pool/overview
32
type commitSweepResolver struct {
33
        // localChanCfg is used to provide the resolver with the keys required
34
        // to identify whether the commitment transaction was broadcast by the
35
        // local or remote party.
36
        localChanCfg channeldb.ChannelConfig
37

38
        // commitResolution contains all data required to successfully sweep
39
        // this HTLC on-chain.
40
        commitResolution lnwallet.CommitOutputResolution
41

42
        // broadcastHeight is the height that the original contract was
43
        // broadcast to the main-chain at. We'll use this value to bound any
44
        // historical queries to the chain for spends/confirmations.
45
        broadcastHeight uint32
46

47
        // chanPoint is the channel point of the original contract.
48
        chanPoint wire.OutPoint
49

50
        // channelInitiator denotes whether the party responsible for resolving
51
        // the contract initiated the channel.
52
        channelInitiator bool
53

54
        // leaseExpiry denotes the additional waiting period the contract must
55
        // hold until it can be resolved. This waiting period is known as the
56
        // expiration of a script-enforced leased channel and only applies to
57
        // the channel initiator.
58
        //
59
        // NOTE: This value should only be set when the contract belongs to a
60
        // leased channel.
61
        leaseExpiry uint32
62

63
        // chanType denotes the type of channel the contract belongs to.
64
        chanType channeldb.ChannelType
65

66
        // currentReport stores the current state of the resolver for reporting
67
        // over the rpc interface.
68
        currentReport ContractReport
69

70
        // reportLock prevents concurrent access to the resolver report.
71
        reportLock sync.Mutex
72

73
        contractResolverKit
74
}
75

76
// newCommitSweepResolver instantiates a new direct commit output resolver.
77
func newCommitSweepResolver(res lnwallet.CommitOutputResolution,
78
        broadcastHeight uint32, chanPoint wire.OutPoint,
79
        resCfg ResolverConfig) *commitSweepResolver {
3✔
80

3✔
81
        r := &commitSweepResolver{
3✔
82
                contractResolverKit: *newContractResolverKit(resCfg),
3✔
83
                commitResolution:    res,
3✔
84
                broadcastHeight:     broadcastHeight,
3✔
85
                chanPoint:           chanPoint,
3✔
86
        }
3✔
87

3✔
88
        r.initLogger(fmt.Sprintf("%T(%v)", r, r.commitResolution.SelfOutPoint))
3✔
89
        r.initReport()
3✔
90

3✔
91
        return r
3✔
92
}
3✔
93

94
// ResolverKey returns an identifier which should be globally unique for this
95
// particular resolver within the chain the original contract resides within.
96
func (c *commitSweepResolver) ResolverKey() []byte {
3✔
97
        key := newResolverID(c.commitResolution.SelfOutPoint)
3✔
98
        return key[:]
3✔
99
}
3✔
100

101
// waitForSpend waits for the given outpoint to be spent, and returns the
102
// details of the spending tx.
103
func waitForSpend(op *wire.OutPoint, pkScript []byte, heightHint uint32,
104
        notifier chainntnfs.ChainNotifier, quit <-chan struct{}) (
105
        *chainntnfs.SpendDetail, error) {
30✔
106

30✔
107
        spendNtfn, err := notifier.RegisterSpendNtfn(
30✔
108
                op, pkScript, heightHint,
30✔
109
        )
30✔
110
        if err != nil {
30✔
111
                return nil, err
×
112
        }
×
113

114
        select {
30✔
115
        case spendDetail, ok := <-spendNtfn.Spend:
30✔
116
                if !ok {
30✔
117
                        return nil, errResolverShuttingDown
×
118
                }
×
119

120
                return spendDetail, nil
30✔
121

UNCOV
122
        case <-quit:
×
UNCOV
123
                return nil, errResolverShuttingDown
×
124
        }
125
}
126

127
// getCommitTxConfHeight waits for confirmation of the commitment tx and
128
// returns the confirmation height.
129
func (c *commitSweepResolver) getCommitTxConfHeight() (uint32, error) {
3✔
130
        txID := c.commitResolution.SelfOutPoint.Hash
3✔
131
        signDesc := c.commitResolution.SelfOutputSignDesc
3✔
132
        pkScript := signDesc.Output.PkScript
3✔
133

3✔
134
        const confDepth = 1
3✔
135

3✔
136
        confChan, err := c.Notifier.RegisterConfirmationsNtfn(
3✔
137
                &txID, pkScript, confDepth, c.broadcastHeight,
3✔
138
        )
3✔
139
        if err != nil {
3✔
140
                return 0, err
×
141
        }
×
142
        defer confChan.Cancel()
3✔
143

3✔
144
        select {
3✔
145
        case txConfirmation, ok := <-confChan.Confirmed:
3✔
146
                if !ok {
3✔
147
                        return 0, fmt.Errorf("cannot get confirmation "+
×
148
                                "for commit tx %v", txID)
×
149
                }
×
150

151
                return txConfirmation.BlockHeight, nil
3✔
152

153
        case <-c.quit:
×
154
                return 0, errResolverShuttingDown
×
155
        }
156
}
157

158
// Resolve instructs the contract resolver to resolve the output on-chain. Once
159
// the output has been *fully* resolved, the function should return immediately
160
// with a nil ContractResolver value for the first return value.  In the case
161
// that the contract requires further resolution, then another resolve is
162
// returned.
163
//
164
// NOTE: This function MUST be run as a goroutine.
165

166
// TODO(yy): fix the funlen in the next PR.
167
//
168
//nolint:funlen
169
func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
3✔
170
        // If we're already resolved, then we can exit early.
3✔
171
        if c.IsResolved() {
3✔
172
                c.log.Errorf("already resolved")
×
173
                return nil, nil
×
174
        }
×
175

176
        var sweepTxID chainhash.Hash
3✔
177

3✔
178
        // Sweeper is going to join this input with other inputs if possible
3✔
179
        // and publish the sweep tx. When the sweep tx confirms, it signals us
3✔
180
        // through the result channel with the outcome. Wait for this to
3✔
181
        // happen.
3✔
182
        outcome := channeldb.ResolverOutcomeClaimed
3✔
183
        select {
3✔
184
        case sweepResult := <-c.sweepResultChan:
3✔
185
                switch sweepResult.Err {
3✔
186
                // If the remote party was able to sweep this output it's
187
                // likely what we sent was actually a revoked commitment.
188
                // Report the error and continue to wrap up the contract.
189
                case sweep.ErrRemoteSpend:
1✔
190
                        c.log.Warnf("local commitment output was swept by "+
1✔
191
                                "remote party via %v", sweepResult.Tx.TxHash())
1✔
192
                        outcome = channeldb.ResolverOutcomeUnclaimed
1✔
193

194
                // No errors, therefore continue processing.
195
                case nil:
2✔
196
                        c.log.Infof("local commitment output fully resolved by "+
2✔
197
                                "sweep tx: %v", sweepResult.Tx.TxHash())
2✔
198
                // Unknown errors.
199
                default:
×
200
                        c.log.Errorf("unable to sweep input: %v",
×
201
                                sweepResult.Err)
×
202

×
203
                        return nil, sweepResult.Err
×
204
                }
205

206
                sweepTxID = sweepResult.Tx.TxHash()
3✔
207

UNCOV
208
        case <-c.quit:
×
UNCOV
209
                return nil, errResolverShuttingDown
×
210
        }
211

212
        // Funds have been swept and balance is no longer in limbo.
213
        c.reportLock.Lock()
3✔
214
        if outcome == channeldb.ResolverOutcomeClaimed {
5✔
215
                // We only record the balance as recovered if it actually came
2✔
216
                // back to us.
2✔
217
                c.currentReport.RecoveredBalance = c.currentReport.LimboBalance
2✔
218
        }
2✔
219
        c.currentReport.LimboBalance = 0
3✔
220
        c.reportLock.Unlock()
3✔
221
        report := c.currentReport.resolverReport(
3✔
222
                &sweepTxID, channeldb.ResolverTypeCommit, outcome,
3✔
223
        )
3✔
224
        c.markResolved()
3✔
225

3✔
226
        // Checkpoint the resolver with a closure that will write the outcome
3✔
227
        // of the resolver and its sweep transaction to disk.
3✔
228
        return nil, c.Checkpoint(c, report)
3✔
229
}
230

231
// Stop signals the resolver to cancel any current resolution processes, and
232
// suspend.
233
//
234
// NOTE: Part of the ContractResolver interface.
UNCOV
235
func (c *commitSweepResolver) Stop() {
×
UNCOV
236
        c.log.Debugf("stopping...")
×
UNCOV
237
        defer c.log.Debugf("stopped")
×
UNCOV
238
        close(c.quit)
×
UNCOV
239
}
×
240

241
// SupplementState allows the user of a ContractResolver to supplement it with
242
// state required for the proper resolution of a contract.
243
//
244
// NOTE: Part of the ContractResolver interface.
UNCOV
245
func (c *commitSweepResolver) SupplementState(state *channeldb.OpenChannel) {
×
UNCOV
246
        if state.ChanType.HasLeaseExpiration() {
×
UNCOV
247
                c.leaseExpiry = state.ThawHeight
×
UNCOV
248
        }
×
UNCOV
249
        c.localChanCfg = state.LocalChanCfg
×
UNCOV
250
        c.channelInitiator = state.IsInitiator
×
UNCOV
251
        c.chanType = state.ChanType
×
252
}
253

254
// hasCLTV denotes whether the resolver must wait for an additional CLTV to
255
// expire before resolving the contract.
256
func (c *commitSweepResolver) hasCLTV() bool {
8✔
257
        return c.channelInitiator && c.leaseExpiry > 0
8✔
258
}
8✔
259

260
// Encode writes an encoded version of the ContractResolver into the passed
261
// Writer.
262
//
263
// NOTE: Part of the ContractResolver interface.
264
func (c *commitSweepResolver) Encode(w io.Writer) error {
1✔
265
        if err := encodeCommitResolution(w, &c.commitResolution); err != nil {
1✔
266
                return err
×
267
        }
×
268

269
        if err := binary.Write(w, endian, c.IsResolved()); err != nil {
1✔
270
                return err
×
271
        }
×
272
        if err := binary.Write(w, endian, c.broadcastHeight); err != nil {
1✔
273
                return err
×
274
        }
×
275
        if _, err := w.Write(c.chanPoint.Hash[:]); err != nil {
1✔
276
                return err
×
277
        }
×
278
        err := binary.Write(w, endian, c.chanPoint.Index)
1✔
279
        if err != nil {
1✔
280
                return err
×
281
        }
×
282

283
        // Previously a sweep tx was serialized at this point. Refactoring
284
        // removed this, but keep in mind that this data may still be present in
285
        // the database.
286

287
        return nil
1✔
288
}
289

290
// newCommitSweepResolverFromReader attempts to decode an encoded
291
// ContractResolver from the passed Reader instance, returning an active
292
// ContractResolver instance.
293
func newCommitSweepResolverFromReader(r io.Reader, resCfg ResolverConfig) (
294
        *commitSweepResolver, error) {
1✔
295

1✔
296
        c := &commitSweepResolver{
1✔
297
                contractResolverKit: *newContractResolverKit(resCfg),
1✔
298
        }
1✔
299

1✔
300
        if err := decodeCommitResolution(r, &c.commitResolution); err != nil {
1✔
301
                return nil, err
×
302
        }
×
303

304
        var resolved bool
1✔
305
        if err := binary.Read(r, endian, &resolved); err != nil {
1✔
306
                return nil, err
×
307
        }
×
308
        if resolved {
1✔
309
                c.markResolved()
×
310
        }
×
311

312
        if err := binary.Read(r, endian, &c.broadcastHeight); err != nil {
1✔
313
                return nil, err
×
314
        }
×
315
        _, err := io.ReadFull(r, c.chanPoint.Hash[:])
1✔
316
        if err != nil {
1✔
317
                return nil, err
×
318
        }
×
319
        err = binary.Read(r, endian, &c.chanPoint.Index)
1✔
320
        if err != nil {
1✔
321
                return nil, err
×
322
        }
×
323

324
        // Previously a sweep tx was deserialized at this point. Refactoring
325
        // removed this, but keep in mind that this data may still be present in
326
        // the database.
327

328
        c.initLogger(fmt.Sprintf("%T(%v)", c, c.commitResolution.SelfOutPoint))
1✔
329
        c.initReport()
1✔
330

1✔
331
        return c, nil
1✔
332
}
333

334
// report returns a report on the resolution state of the contract.
335
func (c *commitSweepResolver) report() *ContractReport {
6✔
336
        c.reportLock.Lock()
6✔
337
        defer c.reportLock.Unlock()
6✔
338

6✔
339
        cpy := c.currentReport
6✔
340
        return &cpy
6✔
341
}
6✔
342

343
// initReport initializes the pending channels report for this resolver.
344
func (c *commitSweepResolver) initReport() {
4✔
345
        amt := btcutil.Amount(
4✔
346
                c.commitResolution.SelfOutputSignDesc.Output.Value,
4✔
347
        )
4✔
348

4✔
349
        // Set the initial report. All fields are filled in, except for the
4✔
350
        // maturity height which remains 0 until Resolve() is executed.
4✔
351
        //
4✔
352
        // TODO(joostjager): Resolvers only activate after the commit tx
4✔
353
        // confirms. With more refactoring in channel arbitrator, it would be
4✔
354
        // possible to make the confirmation height part of ResolverConfig and
4✔
355
        // populate MaturityHeight here.
4✔
356
        c.currentReport = ContractReport{
4✔
357
                Outpoint:         c.commitResolution.SelfOutPoint,
4✔
358
                Type:             ReportOutputUnencumbered,
4✔
359
                Amount:           amt,
4✔
360
                LimboBalance:     amt,
4✔
361
                RecoveredBalance: 0,
4✔
362
        }
4✔
363
}
4✔
364

365
// A compile time assertion to ensure commitSweepResolver meets the
366
// ContractResolver interface.
367
var _ reportingContractResolver = (*commitSweepResolver)(nil)
368

369
// Launch constructs a commit input and offers it to the sweeper.
370
func (c *commitSweepResolver) Launch() error {
3✔
371
        if c.isLaunched() {
3✔
UNCOV
372
                c.log.Tracef("already launched")
×
UNCOV
373
                return nil
×
UNCOV
374
        }
×
375

376
        c.log.Debugf("launching resolver...")
3✔
377
        c.markLaunched()
3✔
378

3✔
379
        // If we're already resolved, then we can exit early.
3✔
380
        if c.IsResolved() {
3✔
381
                c.log.Errorf("already resolved")
×
382
                return nil
×
383
        }
×
384

385
        confHeight, err := c.getCommitTxConfHeight()
3✔
386
        if err != nil {
3✔
387
                return err
×
388
        }
×
389

390
        // Wait up until the CSV expires, unless we also have a CLTV that
391
        // expires after.
392
        unlockHeight := confHeight + c.commitResolution.MaturityDelay
3✔
393
        if c.hasCLTV() {
3✔
UNCOV
394
                unlockHeight = uint32(math.Max(
×
UNCOV
395
                        float64(unlockHeight), float64(c.leaseExpiry),
×
UNCOV
396
                ))
×
UNCOV
397
        }
×
398

399
        // Update report now that we learned the confirmation height.
400
        c.reportLock.Lock()
3✔
401
        c.currentReport.MaturityHeight = unlockHeight
3✔
402
        c.reportLock.Unlock()
3✔
403

3✔
404
        // Derive the witness type for this input.
3✔
405
        witnessType, err := c.decideWitnessType()
3✔
406
        if err != nil {
3✔
407
                return err
×
408
        }
×
409

410
        // We'll craft an input with all the information required for the
411
        // sweeper to create a fully valid sweeping transaction to recover
412
        // these coins.
413
        var inp *input.BaseInput
3✔
414
        if c.hasCLTV() {
3✔
UNCOV
415
                inp = input.NewCsvInputWithCltv(
×
UNCOV
416
                        &c.commitResolution.SelfOutPoint, witnessType,
×
UNCOV
417
                        &c.commitResolution.SelfOutputSignDesc,
×
UNCOV
418
                        c.broadcastHeight, c.commitResolution.MaturityDelay,
×
UNCOV
419
                        c.leaseExpiry,
×
UNCOV
420
                )
×
421
        } else {
3✔
422
                inp = input.NewCsvInput(
3✔
423
                        &c.commitResolution.SelfOutPoint, witnessType,
3✔
424
                        &c.commitResolution.SelfOutputSignDesc,
3✔
425
                        c.broadcastHeight, c.commitResolution.MaturityDelay,
3✔
426
                )
3✔
427
        }
3✔
428

429
        // TODO(roasbeef): instead of ading ctrl block to the sign desc, make
430
        // new input type, have sweeper set it?
431

432
        // Calculate the budget for the sweeping this input.
433
        budget := calculateBudget(
3✔
434
                btcutil.Amount(inp.SignDesc().Output.Value),
3✔
435
                c.Budget.ToLocalRatio, c.Budget.ToLocal,
3✔
436
        )
3✔
437
        c.log.Infof("sweeping commit output %v using budget=%v", witnessType,
3✔
438
                budget)
3✔
439

3✔
440
        // With our input constructed, we'll now offer it to the sweeper.
3✔
441
        resultChan, err := c.Sweeper.SweepInput(
3✔
442
                inp, sweep.Params{
3✔
443
                        Budget: budget,
3✔
444

3✔
445
                        // Specify a nil deadline here as there's no time
3✔
446
                        // pressure.
3✔
447
                        DeadlineHeight: fn.None[int32](),
3✔
448
                },
3✔
449
        )
3✔
450
        if err != nil {
3✔
451
                c.log.Errorf("unable to sweep input: %v", err)
×
452

×
453
                return err
×
454
        }
×
455

456
        c.sweepResultChan = resultChan
3✔
457

3✔
458
        return nil
3✔
459
}
460

461
// decideWitnessType returns the witness type for the input.
462
func (c *commitSweepResolver) decideWitnessType() (input.WitnessType, error) {
3✔
463
        var (
3✔
464
                isLocalCommitTx bool
3✔
465
                signDesc        = c.commitResolution.SelfOutputSignDesc
3✔
466
        )
3✔
467

3✔
468
        switch {
3✔
469
        // For taproot channels, we'll know if this is the local commit based
470
        // on the timelock value. For remote commitment transactions, the
471
        // witness script has a timelock of 1.
UNCOV
472
        case c.chanType.IsTaproot():
×
UNCOV
473
                delayKey := c.localChanCfg.DelayBasePoint.PubKey
×
UNCOV
474
                nonDelayKey := c.localChanCfg.PaymentBasePoint.PubKey
×
UNCOV
475

×
UNCOV
476
                signKey := c.commitResolution.SelfOutputSignDesc.KeyDesc.PubKey
×
UNCOV
477

×
UNCOV
478
                // If the key in the script is neither of these, we shouldn't
×
UNCOV
479
                // proceed. This should be impossible.
×
UNCOV
480
                if !signKey.IsEqual(delayKey) && !signKey.IsEqual(nonDelayKey) {
×
481
                        return nil, fmt.Errorf("unknown sign key %v", signKey)
×
482
                }
×
483

484
                // The commitment transaction is ours iff the signing key is
485
                // the delay key.
UNCOV
486
                isLocalCommitTx = signKey.IsEqual(delayKey)
×
487

488
        // The output is on our local commitment if the script starts with
489
        // OP_IF for the revocation clause. On the remote commitment it will
490
        // either be a regular P2WKH or a simple sig spend with a CSV delay.
491
        default:
3✔
492
                isLocalCommitTx = signDesc.WitnessScript[0] == txscript.OP_IF
3✔
493
        }
494

495
        isDelayedOutput := c.commitResolution.MaturityDelay != 0
3✔
496

3✔
497
        c.log.Debugf("isDelayedOutput=%v, isLocalCommitTx=%v", isDelayedOutput,
3✔
498
                isLocalCommitTx)
3✔
499

3✔
500
        // There're three types of commitments, those that have tweaks for the
3✔
501
        // remote key (us in this case), those that don't, and a third where
3✔
502
        // there is no tweak and the output is delayed. On the local commitment
3✔
503
        // our output will always be delayed. We'll rely on the presence of the
3✔
504
        // commitment tweak to discern which type of commitment this is.
3✔
505
        var witnessType input.WitnessType
3✔
506
        switch {
3✔
507
        // The local delayed output for a taproot channel.
UNCOV
508
        case isLocalCommitTx && c.chanType.IsTaproot():
×
UNCOV
509
                witnessType = input.TaprootLocalCommitSpend
×
510

511
        // The CSV 1 delayed output for a taproot channel.
UNCOV
512
        case !isLocalCommitTx && c.chanType.IsTaproot():
×
UNCOV
513
                witnessType = input.TaprootRemoteCommitSpend
×
514

515
        // Delayed output to us on our local commitment for a channel lease in
516
        // which we are the initiator.
UNCOV
517
        case isLocalCommitTx && c.hasCLTV():
×
UNCOV
518
                witnessType = input.LeaseCommitmentTimeLock
×
519

520
        // Delayed output to us on our local commitment.
UNCOV
521
        case isLocalCommitTx:
×
UNCOV
522
                witnessType = input.CommitmentTimeLock
×
523

524
        // A confirmed output to us on the remote commitment for a channel lease
525
        // in which we are the initiator.
UNCOV
526
        case isDelayedOutput && c.hasCLTV():
×
UNCOV
527
                witnessType = input.LeaseCommitmentToRemoteConfirmed
×
528

529
        // A confirmed output to us on the remote commitment.
530
        case isDelayedOutput:
2✔
531
                witnessType = input.CommitmentToRemoteConfirmed
2✔
532

533
        // A non-delayed output on the remote commitment where the key is
534
        // tweakless.
535
        case c.commitResolution.SelfOutputSignDesc.SingleTweak == nil:
1✔
536
                witnessType = input.CommitSpendNoDelayTweakless
1✔
537

538
        // A non-delayed output on the remote commitment where the key is
539
        // tweaked.
540
        default:
×
541
                witnessType = input.CommitmentNoDelay
×
542
        }
543

544
        return witnessType, nil
3✔
545
}
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