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

lightningnetwork / lnd / 13536249039

26 Feb 2025 03:42AM UTC coverage: 57.462% (-1.4%) from 58.835%
13536249039

Pull #8453

github

Roasbeef
peer: update chooseDeliveryScript to gen script if needed

In this commit, we update `chooseDeliveryScript` to generate a new
script if needed. This allows us to fold in a few other lines that
always followed this function into this expanded function.

The tests have been updated accordingly.
Pull Request #8453: [4/4] - multi: integrate new rbf coop close FSM into the existing peer flow

275 of 1318 new or added lines in 22 files covered. (20.86%)

19521 existing lines in 257 files now uncovered.

103858 of 180741 relevant lines covered (57.46%)

24750.23 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