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

lightningnetwork / lnd / 12312390362

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

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

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

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

76.91
/contractcourt/htlc_success_resolver.go
1
package contractcourt
2

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

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

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

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

46
        // resolved reflects if the contract has been fully resolved or not.
47
        resolved bool
48

49
        // broadcastHeight is the height that the original contract was
50
        // broadcast to the main-chain at. We'll use this value to bound any
51
        // historical queries to the chain for spends/confirmations.
52
        broadcastHeight uint32
53

54
        // htlc contains information on the htlc that we are resolving on-chain.
55
        htlc channeldb.HTLC
56

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

63
        // reportLock prevents concurrent access to the resolver report.
64
        reportLock sync.Mutex
65

66
        contractResolverKit
67

68
        htlcLeaseResolver
69
}
70

71
// newSuccessResolver instanties a new htlc success resolver.
72
func newSuccessResolver(res lnwallet.IncomingHtlcResolution,
73
        broadcastHeight uint32, htlc channeldb.HTLC,
74
        resCfg ResolverConfig) *htlcSuccessResolver {
×
75

×
76
        h := &htlcSuccessResolver{
×
77
                contractResolverKit: *newContractResolverKit(resCfg),
×
78
                htlcResolution:      res,
×
79
                broadcastHeight:     broadcastHeight,
×
80
                htlc:                htlc,
×
81
        }
×
82

×
83
        h.initReport()
×
84

×
85
        return h
×
86
}
×
87

88
// ResolverKey returns an identifier which should be globally unique for this
89
// particular resolver within the chain the original contract resides within.
90
//
91
// NOTE: Part of the ContractResolver interface.
92
func (h *htlcSuccessResolver) ResolverKey() []byte {
6✔
93
        // The primary key for this resolver will be the outpoint of the HTLC
6✔
94
        // on the commitment transaction itself. If this is our commitment,
6✔
95
        // then the output can be found within the signed success tx,
6✔
96
        // otherwise, it's just the ClaimOutpoint.
6✔
97
        var op wire.OutPoint
6✔
98
        if h.htlcResolution.SignedSuccessTx != nil {
6✔
99
                op = h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
×
100
        } else {
6✔
101
                op = h.htlcResolution.ClaimOutpoint
6✔
102
        }
6✔
103

104
        key := newResolverID(op)
6✔
105
        return key[:]
6✔
106
}
107

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

8✔
121
        // If we're already resolved, then we can exit early.
8✔
122
        if h.resolved {
11✔
123
                return nil, nil
3✔
124
        }
3✔
125

126
        // If we don't have a success transaction, then this means that this is
127
        // an output on the remote party's commitment transaction.
128
        if h.htlcResolution.SignedSuccessTx == nil {
6✔
129
                return h.resolveRemoteCommitOutput(immediate)
1✔
130
        }
1✔
131

132
        // Otherwise this an output on our own commitment, and we must start by
133
        // broadcasting the second-level success transaction.
134
        secondLevelOutpoint, err := h.broadcastSuccessTx(immediate)
4✔
135
        if err != nil {
4✔
136
                return nil, err
×
137
        }
×
138

139
        // To wrap this up, we'll wait until the second-level transaction has
140
        // been spent, then fully resolve the contract.
141
        log.Infof("%T(%x): waiting for second-level HTLC output to be spent "+
4✔
142
                "after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
4✔
143

4✔
144
        spend, err := waitForSpend(
4✔
145
                secondLevelOutpoint,
4✔
146
                h.htlcResolution.SweepSignDesc.Output.PkScript,
4✔
147
                h.broadcastHeight, h.Notifier, h.quit,
4✔
148
        )
4✔
149
        if err != nil {
4✔
150
                return nil, err
×
151
        }
×
152

153
        h.reportLock.Lock()
4✔
154
        h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
4✔
155
        h.currentReport.LimboBalance = 0
4✔
156
        h.reportLock.Unlock()
4✔
157

4✔
158
        h.resolved = true
4✔
159
        return nil, h.checkpointClaim(
4✔
160
                spend.SpenderTxHash, channeldb.ResolverOutcomeClaimed,
4✔
161
        )
4✔
162
}
163

164
// broadcastSuccessTx handles an HTLC output on our local commitment by
165
// broadcasting the second-level success transaction. It returns the ultimate
166
// outpoint of the second-level tx, that we must wait to be spent for the
167
// resolver to be fully resolved.
168
func (h *htlcSuccessResolver) broadcastSuccessTx(
169
        immediate bool) (*wire.OutPoint, error) {
4✔
170

4✔
171
        // If we have non-nil SignDetails, this means that have a 2nd level
4✔
172
        // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY
4✔
173
        // (the case for anchor type channels). In this case we can re-sign it
4✔
174
        // and attach fees at will. We let the sweeper handle this job.  We use
4✔
175
        // the checkpointed outputIncubating field to determine if we already
4✔
176
        // swept the HTLC output into the second level transaction.
4✔
177
        if h.htlcResolution.SignDetails != nil {
6✔
178
                return h.broadcastReSignedSuccessTx(immediate)
2✔
179
        }
2✔
180

181
        // Otherwise we'll publish the second-level transaction directly and
182
        // offer the resolution to the nursery to handle.
183
        log.Infof("%T(%x): broadcasting second-layer transition tx: %v",
2✔
184
                h, h.htlc.RHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx))
2✔
185

2✔
186
        // We'll now broadcast the second layer transaction so we can kick off
2✔
187
        // the claiming process.
2✔
188
        //
2✔
189
        // TODO(roasbeef): after changing sighashes send to tx bundler
2✔
190
        label := labels.MakeLabel(
2✔
191
                labels.LabelTypeChannelClose, &h.ShortChanID,
2✔
192
        )
2✔
193
        err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
2✔
194
        if err != nil {
2✔
195
                return nil, err
×
196
        }
×
197

198
        // Otherwise, this is an output on our commitment transaction. In this
199
        // case, we'll send it to the incubator, but only if we haven't already
200
        // done so.
201
        if !h.outputIncubating {
3✔
202
                log.Infof("%T(%x): incubating incoming htlc output",
1✔
203
                        h, h.htlc.RHash[:])
1✔
204

1✔
205
                err := h.IncubateOutputs(
1✔
206
                        h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
1✔
207
                        fn.Some(h.htlcResolution),
1✔
208
                        h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
1✔
209
                )
1✔
210
                if err != nil {
1✔
211
                        return nil, err
×
212
                }
×
213

214
                h.outputIncubating = true
1✔
215

1✔
216
                if err := h.Checkpoint(h); err != nil {
1✔
217
                        log.Errorf("unable to Checkpoint: %v", err)
×
218
                        return nil, err
×
219
                }
×
220
        }
221

222
        return &h.htlcResolution.ClaimOutpoint, nil
2✔
223
}
224

225
// broadcastReSignedSuccessTx handles the case where we have non-nil
226
// SignDetails, and offers the second level transaction to the Sweeper, that
227
// will re-sign it and attach fees at will.
228
//
229
//nolint:funlen
230
func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) (
231
        *wire.OutPoint, error) {
2✔
232

2✔
233
        // Keep track of the tx spending the HTLC output on the commitment, as
2✔
234
        // this will be the confirmed second-level tx we'll ultimately sweep.
2✔
235
        var commitSpend *chainntnfs.SpendDetail
2✔
236

2✔
237
        // We will have to let the sweeper re-sign the success tx and wait for
2✔
238
        // it to confirm, if we haven't already.
2✔
239
        isTaproot := txscript.IsPayToTaproot(
2✔
240
                h.htlcResolution.SweepSignDesc.Output.PkScript,
2✔
241
        )
2✔
242
        if !h.outputIncubating {
3✔
243
                var secondLevelInput input.HtlcSecondLevelAnchorInput
1✔
244
                if isTaproot {
1✔
245
                        //nolint:ll
×
246
                        secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
×
247
                                h.htlcResolution.SignedSuccessTx,
×
248
                                h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
×
249
                                h.broadcastHeight,
×
250
                                input.WithResolutionBlob(
×
251
                                        h.htlcResolution.ResolutionBlob,
×
252
                                ),
×
253
                        )
×
254
                } else {
1✔
255
                        //nolint:ll
1✔
256
                        secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
1✔
257
                                h.htlcResolution.SignedSuccessTx,
1✔
258
                                h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
1✔
259
                                h.broadcastHeight,
1✔
260
                        )
1✔
261
                }
1✔
262

263
                // Calculate the budget for this sweep.
264
                value := btcutil.Amount(
1✔
265
                        secondLevelInput.SignDesc().Output.Value,
1✔
266
                )
1✔
267
                budget := calculateBudget(
1✔
268
                        value, h.Budget.DeadlineHTLCRatio,
1✔
269
                        h.Budget.DeadlineHTLC,
1✔
270
                )
1✔
271

1✔
272
                // The deadline would be the CLTV in this HTLC output. If we
1✔
273
                // are the initiator of this force close, with the default
1✔
274
                // `IncomingBroadcastDelta`, it means we have 10 blocks left
1✔
275
                // when going onchain. Given we need to mine one block to
1✔
276
                // confirm the force close tx, and one more block to trigger
1✔
277
                // the sweep, we have 8 blocks left to sweep the HTLC.
1✔
278
                deadline := fn.Some(int32(h.htlc.RefundTimeout))
1✔
279

1✔
280
                log.Infof("%T(%x): offering second-level HTLC success tx to "+
1✔
281
                        "sweeper with deadline=%v, budget=%v", h,
1✔
282
                        h.htlc.RHash[:], h.htlc.RefundTimeout, budget)
1✔
283

1✔
284
                // We'll now offer the second-level transaction to the sweeper.
1✔
285
                _, err := h.Sweeper.SweepInput(
1✔
286
                        &secondLevelInput,
1✔
287
                        sweep.Params{
1✔
288
                                Budget:         budget,
1✔
289
                                DeadlineHeight: deadline,
1✔
290
                                Immediate:      immediate,
1✔
291
                        },
1✔
292
                )
1✔
293
                if err != nil {
1✔
294
                        return nil, err
×
295
                }
×
296

297
                log.Infof("%T(%x): waiting for second-level HTLC success "+
1✔
298
                        "transaction to confirm", h, h.htlc.RHash[:])
1✔
299

1✔
300
                // Wait for the second level transaction to confirm.
1✔
301
                commitSpend, err = waitForSpend(
1✔
302
                        &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
1✔
303
                        h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
1✔
304
                        h.broadcastHeight, h.Notifier, h.quit,
1✔
305
                )
1✔
306
                if err != nil {
1✔
307
                        return nil, err
×
308
                }
×
309

310
                // Now that the second-level transaction has confirmed, we
311
                // checkpoint the state so we'll go to the next stage in case
312
                // of restarts.
313
                h.outputIncubating = true
1✔
314
                if err := h.Checkpoint(h); err != nil {
1✔
315
                        log.Errorf("unable to Checkpoint: %v", err)
×
316
                        return nil, err
×
317
                }
×
318

319
                log.Infof("%T(%x): second-level HTLC success transaction "+
1✔
320
                        "confirmed!", h, h.htlc.RHash[:])
1✔
321
        }
322

323
        // If we ended up here after a restart, we must again get the
324
        // spend notification.
325
        if commitSpend == nil {
3✔
326
                var err error
1✔
327
                commitSpend, err = waitForSpend(
1✔
328
                        &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
1✔
329
                        h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
1✔
330
                        h.broadcastHeight, h.Notifier, h.quit,
1✔
331
                )
1✔
332
                if err != nil {
1✔
333
                        return nil, err
×
334
                }
×
335
        }
336

337
        // The HTLC success tx has a CSV lock that we must wait for, and if
338
        // this is a lease enforced channel and we're the imitator, we may need
339
        // to wait for longer.
340
        waitHeight := h.deriveWaitHeight(
2✔
341
                h.htlcResolution.CsvDelay, commitSpend,
2✔
342
        )
2✔
343

2✔
344
        // Now that the sweeper has broadcasted the second-level transaction,
2✔
345
        // it has confirmed, and we have checkpointed our state, we'll sweep
2✔
346
        // the second level output. We report the resolver has moved the next
2✔
347
        // stage.
2✔
348
        h.reportLock.Lock()
2✔
349
        h.currentReport.Stage = 2
2✔
350
        h.currentReport.MaturityHeight = waitHeight
2✔
351
        h.reportLock.Unlock()
2✔
352

2✔
353
        if h.hasCLTV() {
2✔
354
                log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
×
355
                        "expire at height %v", h, h.htlc.RHash[:],
×
356
                        waitHeight)
×
357
        } else {
2✔
358
                log.Infof("%T(%x): waiting for CSV lock to expire at "+
2✔
359
                        "height %v", h, h.htlc.RHash[:], waitHeight)
2✔
360
        }
2✔
361

362
        // Deduct one block so this input is offered to the sweeper one block
363
        // earlier since the sweeper will wait for one block to trigger the
364
        // sweeping.
365
        //
366
        // TODO(yy): this is done so the outputs can be aggregated
367
        // properly. Suppose CSV locks of five 2nd-level outputs all
368
        // expire at height 840000, there is a race in block digestion
369
        // between contractcourt and sweeper:
370
        // - G1: block 840000 received in contractcourt, it now offers
371
        //   the outputs to the sweeper.
372
        // - G2: block 840000 received in sweeper, it now starts to
373
        //   sweep the received outputs - there's no guarantee all
374
        //   fives have been received.
375
        // To solve this, we either offer the outputs earlier, or
376
        // implement `blockbeat`, and force contractcourt and sweeper
377
        // to consume each block sequentially.
378
        waitHeight--
2✔
379

2✔
380
        // TODO(yy): let sweeper handles the wait?
2✔
381
        err := waitForHeight(waitHeight, h.Notifier, h.quit)
2✔
382
        if err != nil {
2✔
383
                return nil, err
×
384
        }
×
385

386
        // We'll use this input index to determine the second-level output
387
        // index on the transaction, as the signatures requires the indexes to
388
        // be the same. We don't look for the second-level output script
389
        // directly, as there might be more than one HTLC output to the same
390
        // pkScript.
391
        op := &wire.OutPoint{
2✔
392
                Hash:  *commitSpend.SpenderTxHash,
2✔
393
                Index: commitSpend.SpenderInputIndex,
2✔
394
        }
2✔
395

2✔
396
        // Let the sweeper sweep the second-level output now that the
2✔
397
        // CSV/CLTV locks have expired.
2✔
398
        var witType input.StandardWitnessType
2✔
399
        if isTaproot {
2✔
400
                witType = input.TaprootHtlcAcceptedSuccessSecondLevel
×
401
        } else {
2✔
402
                witType = input.HtlcAcceptedSuccessSecondLevel
2✔
403
        }
2✔
404
        inp := h.makeSweepInput(
2✔
405
                op, witType,
2✔
406
                input.LeaseHtlcAcceptedSuccessSecondLevel,
2✔
407
                &h.htlcResolution.SweepSignDesc,
2✔
408
                h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
2✔
409
                h.htlc.RHash, h.htlcResolution.ResolutionBlob,
2✔
410
        )
2✔
411

2✔
412
        // Calculate the budget for this sweep.
2✔
413
        budget := calculateBudget(
2✔
414
                btcutil.Amount(inp.SignDesc().Output.Value),
2✔
415
                h.Budget.NoDeadlineHTLCRatio,
2✔
416
                h.Budget.NoDeadlineHTLC,
2✔
417
        )
2✔
418

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

2✔
423
        // TODO(roasbeef): need to update above for leased types
2✔
424
        _, err = h.Sweeper.SweepInput(
2✔
425
                inp,
2✔
426
                sweep.Params{
2✔
427
                        Budget: budget,
2✔
428

2✔
429
                        // For second level success tx, there's no rush to get
2✔
430
                        // it confirmed, so we use a nil deadline.
2✔
431
                        DeadlineHeight: fn.None[int32](),
2✔
432
                },
2✔
433
        )
2✔
434
        if err != nil {
2✔
435
                return nil, err
×
436
        }
×
437

438
        // Will return this outpoint, when this is spent the resolver is fully
439
        // resolved.
440
        return op, nil
2✔
441
}
442

443
// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
444
// commitment with the preimage. In this case we can sweep the output directly,
445
// and don't have to broadcast a second-level transaction.
446
func (h *htlcSuccessResolver) resolveRemoteCommitOutput(immediate bool) (
447
        ContractResolver, error) {
1✔
448

1✔
449
        isTaproot := txscript.IsPayToTaproot(
1✔
450
                h.htlcResolution.SweepSignDesc.Output.PkScript,
1✔
451
        )
1✔
452

1✔
453
        // Before we can craft out sweeping transaction, we need to
1✔
454
        // create an input which contains all the items required to add
1✔
455
        // this input to a sweeping transaction, and generate a
1✔
456
        // witness.
1✔
457
        var inp input.Input
1✔
458
        if isTaproot {
1✔
459
                inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
×
460
                        &h.htlcResolution.ClaimOutpoint,
×
461
                        &h.htlcResolution.SweepSignDesc,
×
462
                        h.htlcResolution.Preimage[:],
×
463
                        h.broadcastHeight,
×
464
                        h.htlcResolution.CsvDelay,
×
465
                        input.WithResolutionBlob(
×
466
                                h.htlcResolution.ResolutionBlob,
×
467
                        ),
×
468
                ))
×
469
        } else {
1✔
470
                inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
1✔
471
                        &h.htlcResolution.ClaimOutpoint,
1✔
472
                        &h.htlcResolution.SweepSignDesc,
1✔
473
                        h.htlcResolution.Preimage[:],
1✔
474
                        h.broadcastHeight,
1✔
475
                        h.htlcResolution.CsvDelay,
1✔
476
                ))
1✔
477
        }
1✔
478

479
        // Calculate the budget for this sweep.
480
        budget := calculateBudget(
1✔
481
                btcutil.Amount(inp.SignDesc().Output.Value),
1✔
482
                h.Budget.DeadlineHTLCRatio,
1✔
483
                h.Budget.DeadlineHTLC,
1✔
484
        )
1✔
485

1✔
486
        deadline := fn.Some(int32(h.htlc.RefundTimeout))
1✔
487

1✔
488
        log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
1✔
489
                "with deadline=%v, budget=%v", h, h.htlc.RHash[:],
1✔
490
                h.htlc.RefundTimeout, budget)
1✔
491

1✔
492
        // We'll now offer the direct preimage HTLC to the sweeper.
1✔
493
        _, err := h.Sweeper.SweepInput(
1✔
494
                inp,
1✔
495
                sweep.Params{
1✔
496
                        Budget:         budget,
1✔
497
                        DeadlineHeight: deadline,
1✔
498
                        Immediate:      immediate,
1✔
499
                },
1✔
500
        )
1✔
501
        if err != nil {
1✔
502
                return nil, err
×
503
        }
×
504

505
        // Wait for the direct-preimage HTLC sweep tx to confirm.
506
        sweepTxDetails, err := waitForSpend(
1✔
507
                &h.htlcResolution.ClaimOutpoint,
1✔
508
                h.htlcResolution.SweepSignDesc.Output.PkScript,
1✔
509
                h.broadcastHeight, h.Notifier, h.quit,
1✔
510
        )
1✔
511
        if err != nil {
1✔
512
                return nil, err
×
513
        }
×
514

515
        // Once the transaction has received a sufficient number of
516
        // confirmations, we'll mark ourselves as fully resolved and exit.
517
        h.resolved = true
1✔
518

1✔
519
        // Checkpoint the resolver, and write the outcome to disk.
1✔
520
        return nil, h.checkpointClaim(
1✔
521
                sweepTxDetails.SpenderTxHash,
1✔
522
                channeldb.ResolverOutcomeClaimed,
1✔
523
        )
1✔
524
}
525

526
// checkpointClaim checkpoints the success resolver with the reports it needs.
527
// If this htlc was claimed two stages, it will write reports for both stages,
528
// otherwise it will just write for the single htlc claim.
529
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
530
        outcome channeldb.ResolverOutcome) error {
5✔
531

5✔
532
        // Mark the htlc as final settled.
5✔
533
        err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
5✔
534
                h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
5✔
535
        )
5✔
536
        if err != nil {
5✔
537
                return err
×
538
        }
×
539

540
        // Send notification.
541
        h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
5✔
542
                models.CircuitKey{
5✔
543
                        ChanID: h.ShortChanID,
5✔
544
                        HtlcID: h.htlc.HtlcIndex,
5✔
545
                },
5✔
546
                channeldb.FinalHtlcInfo{
5✔
547
                        Settled:  true,
5✔
548
                        Offchain: false,
5✔
549
                },
5✔
550
        )
5✔
551

5✔
552
        // Create a resolver report for claiming of the htlc itself.
5✔
553
        amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
5✔
554
        reports := []*channeldb.ResolverReport{
5✔
555
                {
5✔
556
                        OutPoint:        h.htlcResolution.ClaimOutpoint,
5✔
557
                        Amount:          amt,
5✔
558
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
5✔
559
                        ResolverOutcome: outcome,
5✔
560
                        SpendTxID:       spendTx,
5✔
561
                },
5✔
562
        }
5✔
563

5✔
564
        // If we have a success tx, we append a report to represent our first
5✔
565
        // stage claim.
5✔
566
        if h.htlcResolution.SignedSuccessTx != nil {
9✔
567
                // If the SignedSuccessTx is not nil, we are claiming the htlc
4✔
568
                // in two stages, so we need to create a report for the first
4✔
569
                // stage transaction as well.
4✔
570
                spendTx := h.htlcResolution.SignedSuccessTx
4✔
571
                spendTxID := spendTx.TxHash()
4✔
572

4✔
573
                report := &channeldb.ResolverReport{
4✔
574
                        OutPoint:        spendTx.TxIn[0].PreviousOutPoint,
4✔
575
                        Amount:          h.htlc.Amt.ToSatoshis(),
4✔
576
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
4✔
577
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
4✔
578
                        SpendTxID:       &spendTxID,
4✔
579
                }
4✔
580
                reports = append(reports, report)
4✔
581
        }
4✔
582

583
        // Finally, we checkpoint the resolver with our report(s).
584
        return h.Checkpoint(h, reports...)
5✔
585
}
586

587
// Stop signals the resolver to cancel any current resolution processes, and
588
// suspend.
589
//
590
// NOTE: Part of the ContractResolver interface.
591
func (h *htlcSuccessResolver) Stop() {
×
592
        close(h.quit)
×
593
}
×
594

595
// IsResolved returns true if the stored state in the resolve is fully
596
// resolved. In this case the target output can be forgotten.
597
//
598
// NOTE: Part of the ContractResolver interface.
599
func (h *htlcSuccessResolver) IsResolved() bool {
×
600
        return h.resolved
×
601
}
×
602

603
// report returns a report on the resolution state of the contract.
604
func (h *htlcSuccessResolver) report() *ContractReport {
×
605
        // If the sign details are nil, the report will be created by handled
×
606
        // by the nursery.
×
607
        if h.htlcResolution.SignDetails == nil {
×
608
                return nil
×
609
        }
×
610

611
        h.reportLock.Lock()
×
612
        defer h.reportLock.Unlock()
×
613
        cpy := h.currentReport
×
614
        return &cpy
×
615
}
616

617
func (h *htlcSuccessResolver) initReport() {
7✔
618
        // We create the initial report. This will only be reported for
7✔
619
        // resolvers not handled by the nursery.
7✔
620
        finalAmt := h.htlc.Amt.ToSatoshis()
7✔
621
        if h.htlcResolution.SignedSuccessTx != nil {
11✔
622
                finalAmt = btcutil.Amount(
4✔
623
                        h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
4✔
624
                )
4✔
625
        }
4✔
626

627
        h.currentReport = ContractReport{
7✔
628
                Outpoint:       h.htlcResolution.ClaimOutpoint,
7✔
629
                Type:           ReportOutputIncomingHtlc,
7✔
630
                Amount:         finalAmt,
7✔
631
                MaturityHeight: h.htlcResolution.CsvDelay,
7✔
632
                LimboBalance:   finalAmt,
7✔
633
                Stage:          1,
7✔
634
        }
7✔
635
}
636

637
// Encode writes an encoded version of the ContractResolver into the passed
638
// Writer.
639
//
640
// NOTE: Part of the ContractResolver interface.
641
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
9✔
642
        // First we'll encode our inner HTLC resolution.
9✔
643
        if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
9✔
644
                return err
×
645
        }
×
646

647
        // Next, we'll write out the fields that are specified to the contract
648
        // resolver.
649
        if err := binary.Write(w, endian, h.outputIncubating); err != nil {
9✔
650
                return err
×
651
        }
×
652
        if err := binary.Write(w, endian, h.resolved); err != nil {
9✔
653
                return err
×
654
        }
×
655
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
9✔
656
                return err
×
657
        }
×
658
        if _, err := w.Write(h.htlc.RHash[:]); err != nil {
9✔
659
                return err
×
660
        }
×
661

662
        // We encode the sign details last for backwards compatibility.
663
        err := encodeSignDetails(w, h.htlcResolution.SignDetails)
9✔
664
        if err != nil {
9✔
665
                return err
×
666
        }
×
667

668
        return nil
9✔
669
}
670

671
// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
672
// from the passed Reader instance, returning an active ContractResolver
673
// instance.
674
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
675
        *htlcSuccessResolver, error) {
7✔
676

7✔
677
        h := &htlcSuccessResolver{
7✔
678
                contractResolverKit: *newContractResolverKit(resCfg),
7✔
679
        }
7✔
680

7✔
681
        // First we'll decode our inner HTLC resolution.
7✔
682
        if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
7✔
683
                return nil, err
×
684
        }
×
685

686
        // Next, we'll read all the fields that are specified to the contract
687
        // resolver.
688
        if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
7✔
689
                return nil, err
×
690
        }
×
691
        if err := binary.Read(r, endian, &h.resolved); err != nil {
7✔
692
                return nil, err
×
693
        }
×
694
        if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
7✔
695
                return nil, err
×
696
        }
×
697
        if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
7✔
698
                return nil, err
×
699
        }
×
700

701
        // Sign details is a new field that was added to the htlc resolution,
702
        // so it is serialized last for backwards compatibility. We try to read
703
        // it, but don't error out if there are not bytes left.
704
        signDetails, err := decodeSignDetails(r)
7✔
705
        if err == nil {
14✔
706
                h.htlcResolution.SignDetails = signDetails
7✔
707
        } else if err != io.EOF && err != io.ErrUnexpectedEOF {
7✔
708
                return nil, err
×
709
        }
×
710

711
        h.initReport()
7✔
712

7✔
713
        return h, nil
7✔
714
}
715

716
// Supplement adds additional information to the resolver that is required
717
// before Resolve() is called.
718
//
719
// NOTE: Part of the htlcContractResolver interface.
720
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
5✔
721
        h.htlc = htlc
5✔
722
}
5✔
723

724
// HtlcPoint returns the htlc's outpoint on the commitment tx.
725
//
726
// NOTE: Part of the htlcContractResolver interface.
727
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
×
728
        return h.htlcResolution.HtlcPoint()
×
729
}
×
730

731
// SupplementDeadline does nothing for an incoming htlc resolver.
732
//
733
// NOTE: Part of the htlcContractResolver interface.
734
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
×
735
}
×
736

737
// A compile time assertion to ensure htlcSuccessResolver meets the
738
// ContractResolver interface.
739
var _ htlcContractResolver = (*htlcSuccessResolver)(nil)
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