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

lightningnetwork / lnd / 12293715361

12 Dec 2024 09:38AM UTC coverage: 57.483% (+7.9%) from 49.538%
12293715361

Pull #9348

github

ziggie1984
github: update goveralls tool

The goverall tool had a bug regarding the module versioning of
golang packages see also
https://github.com/mattn/goveralls/pull/222 for more background.
Goveralls is wrapped by another library to make it available for
github actions. So the relevant PR which is referenced here in
LND is:
https://github.com/shogo82148/actions-goveralls/pull/521.
Pull Request #9348: github: update goveralls tool

101897 of 177264 relevant lines covered (57.48%)

24982.4 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