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

lightningnetwork / lnd / 13157733617

05 Feb 2025 12:49PM UTC coverage: 57.712% (-1.1%) from 58.82%
13157733617

Pull #9447

github

yyforyongyu
sweep: rename methods for clarity

We now rename "third party" to "unknown" as the inputs can be spent via
an older sweeping tx, a third party (anchor), or a remote party (pin).
In fee bumper we don't have the info to distinguish the above cases, and
leave them to be further handled by the sweeper as it has more context.
Pull Request #9447: sweep: start tracking input spending status in the fee bumper

83 of 87 new or added lines in 2 files covered. (95.4%)

19472 existing lines in 252 files now uncovered.

103634 of 179570 relevant lines covered (57.71%)

24840.31 hits per line

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

79.68
/contractcourt/htlc_success_resolver.go
1
package contractcourt
2

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

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

23
// htlcSuccessResolver is a resolver that's capable of sweeping an incoming
24
// HTLC output on-chain. If this is the remote party's commitment, we'll sweep
25
// it directly from the commitment output *immediately*. If this is our
26
// commitment, we'll first broadcast the success transaction, then send it to
27
// the incubator for sweeping. That's it, no need to send any clean up
28
// messages.
29
//
30
// TODO(roasbeef): don't need to broadcast?
31
type htlcSuccessResolver struct {
32
        // htlcResolution is the incoming HTLC resolution for this HTLC. It
33
        // contains everything we need to properly resolve this HTLC.
34
        htlcResolution lnwallet.IncomingHtlcResolution
35

36
        // outputIncubating returns true if we've sent the output to the output
37
        // incubator (utxo nursery). In case the htlcResolution has non-nil
38
        // SignDetails, it means we will let the Sweeper handle broadcasting
39
        // the secondd-level transaction, and sweeping its output. In this case
40
        // we let this field indicate whether we need to broadcast the
41
        // second-level tx (false) or if it has confirmed and we must sweep the
42
        // second-level output (true).
43
        outputIncubating bool
44

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

50
        // htlc contains information on the htlc that we are resolving on-chain.
51
        htlc channeldb.HTLC
52

53
        // currentReport stores the current state of the resolver for reporting
54
        // over the rpc interface. This should only be reported in case we have
55
        // a non-nil SignDetails on the htlcResolution, otherwise the nursery
56
        // will produce reports.
57
        currentReport ContractReport
58

59
        // reportLock prevents concurrent access to the resolver report.
60
        reportLock sync.Mutex
61

62
        contractResolverKit
63

64
        htlcLeaseResolver
65
}
66

67
// newSuccessResolver instanties a new htlc success resolver.
68
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
69
        broadcastHeight uint32, htlc channeldb.HTLC,
UNCOV
70
        resCfg ResolverConfig) *htlcSuccessResolver {
×
UNCOV
71

×
UNCOV
72
        h := &htlcSuccessResolver{
×
UNCOV
73
                contractResolverKit: *newContractResolverKit(resCfg),
×
UNCOV
74
                htlcResolution:      res,
×
UNCOV
75
                broadcastHeight:     broadcastHeight,
×
UNCOV
76
                htlc:                htlc,
×
UNCOV
77
        }
×
UNCOV
78

×
UNCOV
79
        h.initReport()
×
UNCOV
80
        h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
×
UNCOV
81

×
UNCOV
82
        return h
×
UNCOV
83
}
×
84

85
// outpoint returns the outpoint of the HTLC output we're attempting to sweep.
86
func (h *htlcSuccessResolver) outpoint() wire.OutPoint {
13✔
87
        // The primary key for this resolver will be the outpoint of the HTLC
13✔
88
        // on the commitment transaction itself. If this is our commitment,
13✔
89
        // then the output can be found within the signed success tx,
13✔
90
        // otherwise, it's just the ClaimOutpoint.
13✔
91
        if h.htlcResolution.SignedSuccessTx != nil {
17✔
92
                return h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
4✔
93
        }
4✔
94

95
        return h.htlcResolution.ClaimOutpoint
9✔
96
}
97

98
// ResolverKey returns an identifier which should be globally unique for this
99
// particular resolver within the chain the original contract resides within.
100
//
101
// NOTE: Part of the ContractResolver interface.
102
func (h *htlcSuccessResolver) ResolverKey() []byte {
6✔
103
        key := newResolverID(h.outpoint())
6✔
104
        return key[:]
6✔
105
}
6✔
106

107
// Resolve attempts to resolve an unresolved incoming HTLC that we know the
108
// preimage to. If the HTLC is on the commitment of the remote party, then we'll
109
// simply sweep it directly. Otherwise, we'll hand this off to the utxo nursery
110
// to do its duty. There is no need to make a call to the invoice registry
111
// anymore. Every HTLC has already passed through the incoming contest resolver
112
// and in there the invoice was already marked as settled.
113
//
114
// NOTE: Part of the ContractResolver interface.
115
//
116
// TODO(yy): refactor the interface method to return an error only.
117
func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
8✔
118
        var err error
8✔
119

8✔
120
        switch {
8✔
121
        // If we're already resolved, then we can exit early.
122
        case h.IsResolved():
3✔
123
                h.log.Errorf("already resolved")
3✔
124

125
        // If this is an output on the remote party's commitment transaction,
126
        // use the direct-spend path to sweep the htlc.
127
        case h.isRemoteCommitOutput():
1✔
128
                err = h.resolveRemoteCommitOutput()
1✔
129

130
        // If this is an output on our commitment transaction using post-anchor
131
        // channel type, it will be handled by the sweeper.
132
        case h.isZeroFeeOutput():
2✔
133
                err = h.resolveSuccessTx()
2✔
134

135
        // If this is an output on our own commitment using pre-anchor channel
136
        // type, we will publish the success tx and offer the output to the
137
        // nursery.
138
        default:
2✔
139
                err = h.resolveLegacySuccessTx()
2✔
140
        }
141

142
        return nil, err
8✔
143
}
144

145
// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
146
// commitment with the preimage. In this case we can sweep the output directly,
147
// and don't have to broadcast a second-level transaction.
148
func (h *htlcSuccessResolver) resolveRemoteCommitOutput() error {
1✔
149
        h.log.Info("waiting for direct-preimage spend of the htlc to confirm")
1✔
150

1✔
151
        // Wait for the direct-preimage HTLC sweep tx to confirm.
1✔
152
        //
1✔
153
        // TODO(yy): use the result chan returned from `SweepInput`.
1✔
154
        sweepTxDetails, err := waitForSpend(
1✔
155
                &h.htlcResolution.ClaimOutpoint,
1✔
156
                h.htlcResolution.SweepSignDesc.Output.PkScript,
1✔
157
                h.broadcastHeight, h.Notifier, h.quit,
1✔
158
        )
1✔
159
        if err != nil {
1✔
UNCOV
160
                return err
×
UNCOV
161
        }
×
162

163
        // TODO(yy): should also update the `RecoveredBalance` and
164
        // `LimboBalance` like other paths?
165

166
        // Checkpoint the resolver, and write the outcome to disk.
167
        return h.checkpointClaim(sweepTxDetails.SpenderTxHash)
1✔
168
}
169

170
// checkpointClaim checkpoints the success resolver with the reports it needs.
171
// If this htlc was claimed two stages, it will write reports for both stages,
172
// otherwise it will just write for the single htlc claim.
173
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash) error {
5✔
174
        // Mark the htlc as final settled.
5✔
175
        err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
5✔
176
                h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
5✔
177
        )
5✔
178
        if err != nil {
5✔
179
                return err
×
180
        }
×
181

182
        // Send notification.
183
        h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
5✔
184
                models.CircuitKey{
5✔
185
                        ChanID: h.ShortChanID,
5✔
186
                        HtlcID: h.htlc.HtlcIndex,
5✔
187
                },
5✔
188
                channeldb.FinalHtlcInfo{
5✔
189
                        Settled:  true,
5✔
190
                        Offchain: false,
5✔
191
                },
5✔
192
        )
5✔
193

5✔
194
        // Create a resolver report for claiming of the htlc itself.
5✔
195
        amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
5✔
196
        reports := []*channeldb.ResolverReport{
5✔
197
                {
5✔
198
                        OutPoint:        h.htlcResolution.ClaimOutpoint,
5✔
199
                        Amount:          amt,
5✔
200
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
5✔
201
                        ResolverOutcome: channeldb.ResolverOutcomeClaimed,
5✔
202
                        SpendTxID:       spendTx,
5✔
203
                },
5✔
204
        }
5✔
205

5✔
206
        // If we have a success tx, we append a report to represent our first
5✔
207
        // stage claim.
5✔
208
        if h.htlcResolution.SignedSuccessTx != nil {
9✔
209
                // If the SignedSuccessTx is not nil, we are claiming the htlc
4✔
210
                // in two stages, so we need to create a report for the first
4✔
211
                // stage transaction as well.
4✔
212
                spendTx := h.htlcResolution.SignedSuccessTx
4✔
213
                spendTxID := spendTx.TxHash()
4✔
214

4✔
215
                report := &channeldb.ResolverReport{
4✔
216
                        OutPoint:        spendTx.TxIn[0].PreviousOutPoint,
4✔
217
                        Amount:          h.htlc.Amt.ToSatoshis(),
4✔
218
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
4✔
219
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
4✔
220
                        SpendTxID:       &spendTxID,
4✔
221
                }
4✔
222
                reports = append(reports, report)
4✔
223
        }
4✔
224

225
        // Finally, we checkpoint the resolver with our report(s).
226
        h.markResolved()
5✔
227
        return h.Checkpoint(h, reports...)
5✔
228
}
229

230
// Stop signals the resolver to cancel any current resolution processes, and
231
// suspend.
232
//
233
// NOTE: Part of the ContractResolver interface.
UNCOV
234
func (h *htlcSuccessResolver) Stop() {
×
UNCOV
235
        h.log.Debugf("stopping...")
×
UNCOV
236
        defer h.log.Debugf("stopped")
×
UNCOV
237

×
UNCOV
238
        close(h.quit)
×
UNCOV
239
}
×
240

241
// report returns a report on the resolution state of the contract.
UNCOV
242
func (h *htlcSuccessResolver) report() *ContractReport {
×
UNCOV
243
        // If the sign details are nil, the report will be created by handled
×
UNCOV
244
        // by the nursery.
×
UNCOV
245
        if h.htlcResolution.SignDetails == nil {
×
UNCOV
246
                return nil
×
UNCOV
247
        }
×
248

UNCOV
249
        h.reportLock.Lock()
×
UNCOV
250
        defer h.reportLock.Unlock()
×
UNCOV
251
        cpy := h.currentReport
×
UNCOV
252
        return &cpy
×
253
}
254

255
func (h *htlcSuccessResolver) initReport() {
7✔
256
        // We create the initial report. This will only be reported for
7✔
257
        // resolvers not handled by the nursery.
7✔
258
        finalAmt := h.htlc.Amt.ToSatoshis()
7✔
259
        if h.htlcResolution.SignedSuccessTx != nil {
11✔
260
                finalAmt = btcutil.Amount(
4✔
261
                        h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
4✔
262
                )
4✔
263
        }
4✔
264

265
        h.currentReport = ContractReport{
7✔
266
                Outpoint:       h.htlcResolution.ClaimOutpoint,
7✔
267
                Type:           ReportOutputIncomingHtlc,
7✔
268
                Amount:         finalAmt,
7✔
269
                MaturityHeight: h.htlcResolution.CsvDelay,
7✔
270
                LimboBalance:   finalAmt,
7✔
271
                Stage:          1,
7✔
272
        }
7✔
273
}
274

275
// Encode writes an encoded version of the ContractResolver into the passed
276
// Writer.
277
//
278
// NOTE: Part of the ContractResolver interface.
279
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
9✔
280
        // First we'll encode our inner HTLC resolution.
9✔
281
        if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
9✔
282
                return err
×
283
        }
×
284

285
        // Next, we'll write out the fields that are specified to the contract
286
        // resolver.
287
        if err := binary.Write(w, endian, h.outputIncubating); err != nil {
9✔
288
                return err
×
289
        }
×
290
        if err := binary.Write(w, endian, h.IsResolved()); err != nil {
9✔
291
                return err
×
292
        }
×
293
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
9✔
294
                return err
×
295
        }
×
296
        if _, err := w.Write(h.htlc.RHash[:]); err != nil {
9✔
297
                return err
×
298
        }
×
299

300
        // We encode the sign details last for backwards compatibility.
301
        err := encodeSignDetails(w, h.htlcResolution.SignDetails)
9✔
302
        if err != nil {
9✔
303
                return err
×
304
        }
×
305

306
        return nil
9✔
307
}
308

309
// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
310
// from the passed Reader instance, returning an active ContractResolver
311
// instance.
312
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
313
        *htlcSuccessResolver, error) {
7✔
314

7✔
315
        h := &htlcSuccessResolver{
7✔
316
                contractResolverKit: *newContractResolverKit(resCfg),
7✔
317
        }
7✔
318

7✔
319
        // First we'll decode our inner HTLC resolution.
7✔
320
        if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
7✔
321
                return nil, err
×
322
        }
×
323

324
        // Next, we'll read all the fields that are specified to the contract
325
        // resolver.
326
        if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
7✔
327
                return nil, err
×
328
        }
×
329

330
        var resolved bool
7✔
331
        if err := binary.Read(r, endian, &resolved); err != nil {
7✔
332
                return nil, err
×
333
        }
×
334
        if resolved {
11✔
335
                h.markResolved()
4✔
336
        }
4✔
337

338
        if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
7✔
339
                return nil, err
×
340
        }
×
341
        if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
7✔
342
                return nil, err
×
343
        }
×
344

345
        // Sign details is a new field that was added to the htlc resolution,
346
        // so it is serialized last for backwards compatibility. We try to read
347
        // it, but don't error out if there are not bytes left.
348
        signDetails, err := decodeSignDetails(r)
7✔
349
        if err == nil {
14✔
350
                h.htlcResolution.SignDetails = signDetails
7✔
351
        } else if err != io.EOF && err != io.ErrUnexpectedEOF {
7✔
352
                return nil, err
×
353
        }
×
354

355
        h.initReport()
7✔
356
        h.initLogger(fmt.Sprintf("%T(%v)", h, h.outpoint()))
7✔
357

7✔
358
        return h, nil
7✔
359
}
360

361
// Supplement adds additional information to the resolver that is required
362
// before Resolve() is called.
363
//
364
// NOTE: Part of the htlcContractResolver interface.
365
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
5✔
366
        h.htlc = htlc
5✔
367
}
5✔
368

369
// HtlcPoint returns the htlc's outpoint on the commitment tx.
370
//
371
// NOTE: Part of the htlcContractResolver interface.
UNCOV
372
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
×
UNCOV
373
        return h.htlcResolution.HtlcPoint()
×
UNCOV
374
}
×
375

376
// SupplementDeadline does nothing for an incoming htlc resolver.
377
//
378
// NOTE: Part of the htlcContractResolver interface.
379
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
×
380
}
×
381

382
// A compile time assertion to ensure htlcSuccessResolver meets the
383
// ContractResolver interface.
384
var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
385

386
// isRemoteCommitOutput returns a bool to indicate whether the htlc output is
387
// on the remote commitment.
388
func (h *htlcSuccessResolver) isRemoteCommitOutput() bool {
13✔
389
        // If we don't have a success transaction, then this means that this is
13✔
390
        // an output on the remote party's commitment transaction.
13✔
391
        return h.htlcResolution.SignedSuccessTx == nil
13✔
392
}
13✔
393

394
// isZeroFeeOutput returns a boolean indicating whether the htlc output is from
395
// a anchor-enabled channel, which uses the sighash SINGLE|ANYONECANPAY.
396
func (h *htlcSuccessResolver) isZeroFeeOutput() bool {
8✔
397
        // If we have non-nil SignDetails, this means it has a 2nd level HTLC
8✔
398
        // transaction that is signed using sighash SINGLE|ANYONECANPAY (the
8✔
399
        // case for anchor type channels). In this case we can re-sign it and
8✔
400
        // attach fees at will.
8✔
401
        return h.htlcResolution.SignedSuccessTx != nil &&
8✔
402
                h.htlcResolution.SignDetails != nil
8✔
403
}
8✔
404

405
// isTaproot returns true if the resolver is for a taproot output.
406
func (h *htlcSuccessResolver) isTaproot() bool {
7✔
407
        return txscript.IsPayToTaproot(
7✔
408
                h.htlcResolution.SweepSignDesc.Output.PkScript,
7✔
409
        )
7✔
410
}
7✔
411

412
// sweepRemoteCommitOutput creates a sweep request to sweep the HTLC output on
413
// the remote commitment via the direct preimage-spend.
414
func (h *htlcSuccessResolver) sweepRemoteCommitOutput() error {
4✔
415
        // Before we can craft out sweeping transaction, we need to create an
4✔
416
        // input which contains all the items required to add this input to a
4✔
417
        // sweeping transaction, and generate a witness.
4✔
418
        var inp input.Input
4✔
419

4✔
420
        if h.isTaproot() {
4✔
UNCOV
421
                inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
×
UNCOV
422
                        &h.htlcResolution.ClaimOutpoint,
×
UNCOV
423
                        &h.htlcResolution.SweepSignDesc,
×
UNCOV
424
                        h.htlcResolution.Preimage[:],
×
UNCOV
425
                        h.broadcastHeight,
×
UNCOV
426
                        h.htlcResolution.CsvDelay,
×
UNCOV
427
                        input.WithResolutionBlob(
×
UNCOV
428
                                h.htlcResolution.ResolutionBlob,
×
UNCOV
429
                        ),
×
UNCOV
430
                ))
×
431
        } else {
4✔
432
                inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
4✔
433
                        &h.htlcResolution.ClaimOutpoint,
4✔
434
                        &h.htlcResolution.SweepSignDesc,
4✔
435
                        h.htlcResolution.Preimage[:],
4✔
436
                        h.broadcastHeight,
4✔
437
                        h.htlcResolution.CsvDelay,
4✔
438
                ))
4✔
439
        }
4✔
440

441
        // Calculate the budget for this sweep.
442
        budget := calculateBudget(
4✔
443
                btcutil.Amount(inp.SignDesc().Output.Value),
4✔
444
                h.Budget.DeadlineHTLCRatio,
4✔
445
                h.Budget.DeadlineHTLC,
4✔
446
        )
4✔
447

4✔
448
        deadline := fn.Some(int32(h.htlc.RefundTimeout))
4✔
449

4✔
450
        log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
4✔
451
                "with deadline=%v, budget=%v", h, h.htlc.RHash[:],
4✔
452
                h.htlc.RefundTimeout, budget)
4✔
453

4✔
454
        // We'll now offer the direct preimage HTLC to the sweeper.
4✔
455
        _, err := h.Sweeper.SweepInput(
4✔
456
                inp,
4✔
457
                sweep.Params{
4✔
458
                        Budget:         budget,
4✔
459
                        DeadlineHeight: deadline,
4✔
460
                },
4✔
461
        )
4✔
462

4✔
463
        return err
4✔
464
}
465

466
// sweepSuccessTx attempts to sweep the second level success tx.
467
func (h *htlcSuccessResolver) sweepSuccessTx() error {
1✔
468
        var secondLevelInput input.HtlcSecondLevelAnchorInput
1✔
469
        if h.isTaproot() {
1✔
UNCOV
470
                secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
×
UNCOV
471
                        h.htlcResolution.SignedSuccessTx,
×
UNCOV
472
                        h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
×
UNCOV
473
                        h.broadcastHeight, input.WithResolutionBlob(
×
UNCOV
474
                                h.htlcResolution.ResolutionBlob,
×
UNCOV
475
                        ),
×
UNCOV
476
                )
×
477
        } else {
1✔
478
                secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
1✔
479
                        h.htlcResolution.SignedSuccessTx,
1✔
480
                        h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
1✔
481
                        h.broadcastHeight,
1✔
482
                )
1✔
483
        }
1✔
484

485
        // Calculate the budget for this sweep.
486
        value := btcutil.Amount(secondLevelInput.SignDesc().Output.Value)
1✔
487
        budget := calculateBudget(
1✔
488
                value, h.Budget.DeadlineHTLCRatio, h.Budget.DeadlineHTLC,
1✔
489
        )
1✔
490

1✔
491
        // The deadline would be the CLTV in this HTLC output. If we are the
1✔
492
        // initiator of this force close, with the default
1✔
493
        // `IncomingBroadcastDelta`, it means we have 10 blocks left when going
1✔
494
        // onchain.
1✔
495
        deadline := fn.Some(int32(h.htlc.RefundTimeout))
1✔
496

1✔
497
        h.log.Infof("offering second-level HTLC success tx to sweeper with "+
1✔
498
                "deadline=%v, budget=%v", h.htlc.RefundTimeout, budget)
1✔
499

1✔
500
        // We'll now offer the second-level transaction to the sweeper.
1✔
501
        _, err := h.Sweeper.SweepInput(
1✔
502
                &secondLevelInput,
1✔
503
                sweep.Params{
1✔
504
                        Budget:         budget,
1✔
505
                        DeadlineHeight: deadline,
1✔
506
                },
1✔
507
        )
1✔
508

1✔
509
        return err
1✔
510
}
511

512
// sweepSuccessTxOutput attempts to sweep the output of the second level
513
// success tx.
514
func (h *htlcSuccessResolver) sweepSuccessTxOutput() error {
2✔
515
        h.log.Debugf("sweeping output %v from 2nd-level HTLC success tx",
2✔
516
                h.htlcResolution.ClaimOutpoint)
2✔
517

2✔
518
        // This should be non-blocking as we will only attempt to sweep the
2✔
519
        // output when the second level tx has already been confirmed. In other
2✔
520
        // words, waitForSpend will return immediately.
2✔
521
        commitSpend, err := waitForSpend(
2✔
522
                &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
2✔
523
                h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
2✔
524
                h.broadcastHeight, h.Notifier, h.quit,
2✔
525
        )
2✔
526
        if err != nil {
2✔
527
                return err
×
528
        }
×
529

530
        // The HTLC success tx has a CSV lock that we must wait for, and if
531
        // this is a lease enforced channel and we're the imitator, we may need
532
        // to wait for longer.
533
        waitHeight := h.deriveWaitHeight(h.htlcResolution.CsvDelay, commitSpend)
2✔
534

2✔
535
        // Now that the sweeper has broadcasted the second-level transaction,
2✔
536
        // it has confirmed, and we have checkpointed our state, we'll sweep
2✔
537
        // the second level output. We report the resolver has moved the next
2✔
538
        // stage.
2✔
539
        h.reportLock.Lock()
2✔
540
        h.currentReport.Stage = 2
2✔
541
        h.currentReport.MaturityHeight = waitHeight
2✔
542
        h.reportLock.Unlock()
2✔
543

2✔
544
        if h.hasCLTV() {
2✔
UNCOV
545
                log.Infof("%T(%x): waiting for CSV and CLTV lock to expire at "+
×
UNCOV
546
                        "height %v", h, h.htlc.RHash[:], waitHeight)
×
547
        } else {
2✔
548
                log.Infof("%T(%x): waiting for CSV lock to expire at height %v",
2✔
549
                        h, h.htlc.RHash[:], waitHeight)
2✔
550
        }
2✔
551

552
        // We'll use this input index to determine the second-level output
553
        // index on the transaction, as the signatures requires the indexes to
554
        // be the same. We don't look for the second-level output script
555
        // directly, as there might be more than one HTLC output to the same
556
        // pkScript.
557
        op := &wire.OutPoint{
2✔
558
                Hash:  *commitSpend.SpenderTxHash,
2✔
559
                Index: commitSpend.SpenderInputIndex,
2✔
560
        }
2✔
561

2✔
562
        // Let the sweeper sweep the second-level output now that the
2✔
563
        // CSV/CLTV locks have expired.
2✔
564
        var witType input.StandardWitnessType
2✔
565
        if h.isTaproot() {
2✔
UNCOV
566
                witType = input.TaprootHtlcAcceptedSuccessSecondLevel
×
567
        } else {
2✔
568
                witType = input.HtlcAcceptedSuccessSecondLevel
2✔
569
        }
2✔
570
        inp := h.makeSweepInput(
2✔
571
                op, witType,
2✔
572
                input.LeaseHtlcAcceptedSuccessSecondLevel,
2✔
573
                &h.htlcResolution.SweepSignDesc,
2✔
574
                h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
2✔
575
                h.htlc.RHash, h.htlcResolution.ResolutionBlob,
2✔
576
        )
2✔
577

2✔
578
        // Calculate the budget for this sweep.
2✔
579
        budget := calculateBudget(
2✔
580
                btcutil.Amount(inp.SignDesc().Output.Value),
2✔
581
                h.Budget.NoDeadlineHTLCRatio,
2✔
582
                h.Budget.NoDeadlineHTLC,
2✔
583
        )
2✔
584

2✔
585
        log.Infof("%T(%x): offering second-level success tx output to sweeper "+
2✔
586
                "with no deadline and budget=%v at height=%v", h,
2✔
587
                h.htlc.RHash[:], budget, waitHeight)
2✔
588

2✔
589
        // TODO(yy): use the result chan returned from SweepInput.
2✔
590
        _, err = h.Sweeper.SweepInput(
2✔
591
                inp,
2✔
592
                sweep.Params{
2✔
593
                        Budget: budget,
2✔
594

2✔
595
                        // For second level success tx, there's no rush to get
2✔
596
                        // it confirmed, so we use a nil deadline.
2✔
597
                        DeadlineHeight: fn.None[int32](),
2✔
598
                },
2✔
599
        )
2✔
600

2✔
601
        return err
2✔
602
}
603

604
// resolveLegacySuccessTx handles an HTLC output from a pre-anchor type channel
605
// by broadcasting the second-level success transaction.
606
func (h *htlcSuccessResolver) resolveLegacySuccessTx() error {
2✔
607
        // Otherwise we'll publish the second-level transaction directly and
2✔
608
        // offer the resolution to the nursery to handle.
2✔
609
        h.log.Infof("broadcasting legacy second-level success tx: %v",
2✔
610
                h.htlcResolution.SignedSuccessTx.TxHash())
2✔
611

2✔
612
        // We'll now broadcast the second layer transaction so we can kick off
2✔
613
        // the claiming process.
2✔
614
        //
2✔
615
        // TODO(yy): offer it to the sweeper instead.
2✔
616
        label := labels.MakeLabel(
2✔
617
                labels.LabelTypeChannelClose, &h.ShortChanID,
2✔
618
        )
2✔
619
        err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
2✔
620
        if err != nil {
2✔
621
                return err
×
622
        }
×
623

624
        // Fast-forward to resolve the output from the success tx if the it has
625
        // already been sent to the UtxoNursery.
626
        if h.outputIncubating {
3✔
627
                return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
1✔
628
        }
1✔
629

630
        h.log.Infof("incubating incoming htlc output")
1✔
631

1✔
632
        // Send the output to the incubator.
1✔
633
        err = h.IncubateOutputs(
1✔
634
                h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
1✔
635
                fn.Some(h.htlcResolution),
1✔
636
                h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
1✔
637
        )
1✔
638
        if err != nil {
1✔
639
                return err
×
640
        }
×
641

642
        // Mark the output as incubating and checkpoint it.
643
        h.outputIncubating = true
1✔
644
        if err := h.Checkpoint(h); err != nil {
1✔
645
                return err
×
646
        }
×
647

648
        // Move to resolve the output.
649
        return h.resolveSuccessTxOutput(h.htlcResolution.ClaimOutpoint)
1✔
650
}
651

652
// resolveSuccessTx waits for the sweeping tx of the second-level success tx to
653
// confirm and offers the output from the success tx to the sweeper.
654
func (h *htlcSuccessResolver) resolveSuccessTx() error {
2✔
655
        h.log.Infof("waiting for 2nd-level HTLC success transaction to confirm")
2✔
656

2✔
657
        // Create aliases to make the code more readable.
2✔
658
        outpoint := h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
2✔
659
        pkScript := h.htlcResolution.SignDetails.SignDesc.Output.PkScript
2✔
660

2✔
661
        // Wait for the second level transaction to confirm.
2✔
662
        commitSpend, err := waitForSpend(
2✔
663
                &outpoint, pkScript, h.broadcastHeight, h.Notifier, h.quit,
2✔
664
        )
2✔
665
        if err != nil {
2✔
666
                return err
×
667
        }
×
668

669
        // We'll use this input index to determine the second-level output
670
        // index on the transaction, as the signatures requires the indexes to
671
        // be the same. We don't look for the second-level output script
672
        // directly, as there might be more than one HTLC output to the same
673
        // pkScript.
674
        op := wire.OutPoint{
2✔
675
                Hash:  *commitSpend.SpenderTxHash,
2✔
676
                Index: commitSpend.SpenderInputIndex,
2✔
677
        }
2✔
678

2✔
679
        // If the 2nd-stage sweeping has already been started, we can
2✔
680
        // fast-forward to start the resolving process for the stage two
2✔
681
        // output.
2✔
682
        if h.outputIncubating {
3✔
683
                return h.resolveSuccessTxOutput(op)
1✔
684
        }
1✔
685

686
        // Now that the second-level transaction has confirmed, we checkpoint
687
        // the state so we'll go to the next stage in case of restarts.
688
        h.outputIncubating = true
1✔
689
        if err := h.Checkpoint(h); err != nil {
1✔
690
                log.Errorf("unable to Checkpoint: %v", err)
×
691
                return err
×
692
        }
×
693

694
        h.log.Infof("2nd-level HTLC success tx=%v confirmed",
1✔
695
                commitSpend.SpenderTxHash)
1✔
696

1✔
697
        // Send the sweep request for the output from the success tx.
1✔
698
        if err := h.sweepSuccessTxOutput(); err != nil {
1✔
699
                return err
×
700
        }
×
701

702
        return h.resolveSuccessTxOutput(op)
1✔
703
}
704

705
// resolveSuccessTxOutput waits for the spend of the output from the 2nd-level
706
// success tx.
707
func (h *htlcSuccessResolver) resolveSuccessTxOutput(op wire.OutPoint) error {
4✔
708
        // To wrap this up, we'll wait until the second-level transaction has
4✔
709
        // been spent, then fully resolve the contract.
4✔
710
        log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
4✔
711
                "after csv_delay=%v", h, h.htlc.RHash[:],
4✔
712
                h.htlcResolution.CsvDelay)
4✔
713

4✔
714
        spend, err := waitForSpend(
4✔
715
                &op, h.htlcResolution.SweepSignDesc.Output.PkScript,
4✔
716
                h.broadcastHeight, h.Notifier, h.quit,
4✔
717
        )
4✔
718
        if err != nil {
4✔
UNCOV
719
                return err
×
UNCOV
720
        }
×
721

722
        h.reportLock.Lock()
4✔
723
        h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
4✔
724
        h.currentReport.LimboBalance = 0
4✔
725
        h.reportLock.Unlock()
4✔
726

4✔
727
        return h.checkpointClaim(spend.SpenderTxHash)
4✔
728
}
729

730
// Launch creates an input based on the details of the incoming htlc resolution
731
// and offers it to the sweeper.
732
func (h *htlcSuccessResolver) Launch() error {
11✔
733
        if h.isLaunched() {
11✔
UNCOV
734
                h.log.Tracef("already launched")
×
UNCOV
735
                return nil
×
UNCOV
736
        }
×
737

738
        h.log.Debugf("launching resolver...")
11✔
739
        h.markLaunched()
11✔
740

11✔
741
        switch {
11✔
742
        // If we're already resolved, then we can exit early.
743
        case h.IsResolved():
3✔
744
                h.log.Errorf("already resolved")
3✔
745
                return nil
3✔
746

747
        // If this is an output on the remote party's commitment transaction,
748
        // use the direct-spend path.
749
        case h.isRemoteCommitOutput():
4✔
750
                return h.sweepRemoteCommitOutput()
4✔
751

752
        // If this is an anchor type channel, we now sweep either the
753
        // second-level success tx or the output from the second-level success
754
        // tx.
755
        case h.isZeroFeeOutput():
2✔
756
                // If the second-level success tx has already been swept, we
2✔
757
                // can go ahead and sweep its output.
2✔
758
                if h.outputIncubating {
3✔
759
                        return h.sweepSuccessTxOutput()
1✔
760
                }
1✔
761

762
                // Otherwise, sweep the second level tx.
763
                return h.sweepSuccessTx()
1✔
764

765
        // If this is a legacy channel type, the output is handled by the
766
        // nursery via the Resolve so we do nothing here.
767
        //
768
        // TODO(yy): handle the legacy output by offering it to the sweeper.
769
        default:
2✔
770
                return nil
2✔
771
        }
772
}
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