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

lightningnetwork / lnd / 11166428937

03 Oct 2024 05:07PM UTC coverage: 58.738% (-0.08%) from 58.817%
11166428937

push

github

web-flow
Merge pull request #8960 from lightningnetwork/0-19-staging-rebased

[custom channels 5/5]: merge custom channel staging branch into master

657 of 875 new or added lines in 29 files covered. (75.09%)

260 existing lines in 20 files now uncovered.

130540 of 222243 relevant lines covered (58.74%)

28084.43 hits per line

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

89.44
/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/channeldb/models"
16
        "github.com/lightningnetwork/lnd/fn"
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 {
3✔
75

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

3✔
83
        h.initReport()
3✔
84

3✔
85
        return h
3✔
86
}
3✔
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 {
9✔
93
        // The primary key for this resolver will be the outpoint of the HTLC
9✔
94
        // on the commitment transaction itself. If this is our commitment,
9✔
95
        // then the output can be found within the signed success tx,
9✔
96
        // otherwise, it's just the ClaimOutpoint.
9✔
97
        var op wire.OutPoint
9✔
98
        if h.htlcResolution.SignedSuccessTx != nil {
12✔
99
                op = h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint
3✔
100
        } else {
12✔
101
                op = h.htlcResolution.ClaimOutpoint
9✔
102
        }
9✔
103

104
        key := newResolverID(op)
9✔
105
        return key[:]
9✔
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) {
11✔
120

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

126
        // If the HTLC has custom records, then for now we'll pause resolution.
127
        //
128
        // TODO(roasbeef): Implement resolving HTLCs with custom records
129
        // (follow-up PR).
130
        if len(h.htlc.CustomRecords) != 0 {
8✔
NEW
131
                select { //nolint:gosimple
×
NEW
132
                case <-h.quit:
×
NEW
133
                        return nil, errResolverShuttingDown
×
134
                }
135
        }
136

137
        // If we don't have a success transaction, then this means that this is
138
        // an output on the remote party's commitment transaction.
139
        if h.htlcResolution.SignedSuccessTx == nil {
12✔
140
                return h.resolveRemoteCommitOutput(immediate)
4✔
141
        }
4✔
142

143
        // Otherwise this an output on our own commitment, and we must start by
144
        // broadcasting the second-level success transaction.
145
        secondLevelOutpoint, err := h.broadcastSuccessTx(immediate)
7✔
146
        if err != nil {
10✔
147
                return nil, err
3✔
148
        }
3✔
149

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

7✔
155
        spend, err := waitForSpend(
7✔
156
                secondLevelOutpoint,
7✔
157
                h.htlcResolution.SweepSignDesc.Output.PkScript,
7✔
158
                h.broadcastHeight, h.Notifier, h.quit,
7✔
159
        )
7✔
160
        if err != nil {
7✔
161
                return nil, err
×
162
        }
×
163

164
        h.reportLock.Lock()
7✔
165
        h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
7✔
166
        h.currentReport.LimboBalance = 0
7✔
167
        h.reportLock.Unlock()
7✔
168

7✔
169
        h.resolved = true
7✔
170
        return nil, h.checkpointClaim(
7✔
171
                spend.SpenderTxHash, channeldb.ResolverOutcomeClaimed,
7✔
172
        )
7✔
173
}
174

175
// broadcastSuccessTx handles an HTLC output on our local commitment by
176
// broadcasting the second-level success transaction. It returns the ultimate
177
// outpoint of the second-level tx, that we must wait to be spent for the
178
// resolver to be fully resolved.
179
func (h *htlcSuccessResolver) broadcastSuccessTx(
180
        immediate bool) (*wire.OutPoint, error) {
7✔
181

7✔
182
        // If we have non-nil SignDetails, this means that have a 2nd level
7✔
183
        // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY
7✔
184
        // (the case for anchor type channels). In this case we can re-sign it
7✔
185
        // and attach fees at will. We let the sweeper handle this job.  We use
7✔
186
        // the checkpointed outputIncubating field to determine if we already
7✔
187
        // swept the HTLC output into the second level transaction.
7✔
188
        if h.htlcResolution.SignDetails != nil {
12✔
189
                return h.broadcastReSignedSuccessTx(immediate)
5✔
190
        }
5✔
191

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

2✔
197
        // We'll now broadcast the second layer transaction so we can kick off
2✔
198
        // the claiming process.
2✔
199
        //
2✔
200
        // TODO(roasbeef): after changing sighashes send to tx bundler
2✔
201
        label := labels.MakeLabel(
2✔
202
                labels.LabelTypeChannelClose, &h.ShortChanID,
2✔
203
        )
2✔
204
        err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
2✔
205
        if err != nil {
2✔
206
                return nil, err
×
207
        }
×
208

209
        // Otherwise, this is an output on our commitment transaction. In this
210
        // case, we'll send it to the incubator, but only if we haven't already
211
        // done so.
212
        if !h.outputIncubating {
3✔
213
                log.Infof("%T(%x): incubating incoming htlc output",
1✔
214
                        h, h.htlc.RHash[:])
1✔
215

1✔
216
                err := h.IncubateOutputs(
1✔
217
                        h.ChanPoint, fn.None[lnwallet.OutgoingHtlcResolution](),
1✔
218
                        fn.Some(h.htlcResolution),
1✔
219
                        h.broadcastHeight, fn.Some(int32(h.htlc.RefundTimeout)),
1✔
220
                )
1✔
221
                if err != nil {
1✔
222
                        return nil, err
×
223
                }
×
224

225
                h.outputIncubating = true
1✔
226

1✔
227
                if err := h.Checkpoint(h); err != nil {
1✔
228
                        log.Errorf("unable to Checkpoint: %v", err)
×
229
                        return nil, err
×
230
                }
×
231
        }
232

233
        return &h.htlcResolution.ClaimOutpoint, nil
2✔
234
}
235

236
// broadcastReSignedSuccessTx handles the case where we have non-nil
237
// SignDetails, and offers the second level transaction to the Sweeper, that
238
// will re-sign it and attach fees at will.
239
//
240
//nolint:funlen
241
func (h *htlcSuccessResolver) broadcastReSignedSuccessTx(immediate bool) (
242
        *wire.OutPoint, error) {
5✔
243

5✔
244
        // Keep track of the tx spending the HTLC output on the commitment, as
5✔
245
        // this will be the confirmed second-level tx we'll ultimately sweep.
5✔
246
        var commitSpend *chainntnfs.SpendDetail
5✔
247

5✔
248
        // We will have to let the sweeper re-sign the success tx and wait for
5✔
249
        // it to confirm, if we haven't already.
5✔
250
        isTaproot := txscript.IsPayToTaproot(
5✔
251
                h.htlcResolution.SweepSignDesc.Output.PkScript,
5✔
252
        )
5✔
253
        if !h.outputIncubating {
9✔
254
                var secondLevelInput input.HtlcSecondLevelAnchorInput
4✔
255
                if isTaproot {
7✔
256
                        //nolint:lll
3✔
257
                        secondLevelInput = input.MakeHtlcSecondLevelSuccessTaprootInput(
3✔
258
                                h.htlcResolution.SignedSuccessTx,
3✔
259
                                h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
3✔
260
                                h.broadcastHeight,
3✔
261
                        )
3✔
262
                } else {
7✔
263
                        //nolint:lll
4✔
264
                        secondLevelInput = input.MakeHtlcSecondLevelSuccessAnchorInput(
4✔
265
                                h.htlcResolution.SignedSuccessTx,
4✔
266
                                h.htlcResolution.SignDetails, h.htlcResolution.Preimage,
4✔
267
                                h.broadcastHeight,
4✔
268
                        )
4✔
269
                }
4✔
270

271
                // Calculate the budget for this sweep.
272
                value := btcutil.Amount(
4✔
273
                        secondLevelInput.SignDesc().Output.Value,
4✔
274
                )
4✔
275
                budget := calculateBudget(
4✔
276
                        value, h.Budget.DeadlineHTLCRatio,
4✔
277
                        h.Budget.DeadlineHTLC,
4✔
278
                )
4✔
279

4✔
280
                // The deadline would be the CLTV in this HTLC output. If we
4✔
281
                // are the initiator of this force close, with the default
4✔
282
                // `IncomingBroadcastDelta`, it means we have 10 blocks left
4✔
283
                // when going onchain. Given we need to mine one block to
4✔
284
                // confirm the force close tx, and one more block to trigger
4✔
285
                // the sweep, we have 8 blocks left to sweep the HTLC.
4✔
286
                deadline := fn.Some(int32(h.htlc.RefundTimeout))
4✔
287

4✔
288
                log.Infof("%T(%x): offering second-level HTLC success tx to "+
4✔
289
                        "sweeper with deadline=%v, budget=%v", h,
4✔
290
                        h.htlc.RHash[:], h.htlc.RefundTimeout, budget)
4✔
291

4✔
292
                // We'll now offer the second-level transaction to the sweeper.
4✔
293
                _, err := h.Sweeper.SweepInput(
4✔
294
                        &secondLevelInput,
4✔
295
                        sweep.Params{
4✔
296
                                Budget:         budget,
4✔
297
                                DeadlineHeight: deadline,
4✔
298
                                Immediate:      immediate,
4✔
299
                        },
4✔
300
                )
4✔
301
                if err != nil {
4✔
302
                        return nil, err
×
303
                }
×
304

305
                log.Infof("%T(%x): waiting for second-level HTLC success "+
4✔
306
                        "transaction to confirm", h, h.htlc.RHash[:])
4✔
307

4✔
308
                // Wait for the second level transaction to confirm.
4✔
309
                commitSpend, err = waitForSpend(
4✔
310
                        &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
4✔
311
                        h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
4✔
312
                        h.broadcastHeight, h.Notifier, h.quit,
4✔
313
                )
4✔
314
                if err != nil {
7✔
315
                        return nil, err
3✔
316
                }
3✔
317

318
                // Now that the second-level transaction has confirmed, we
319
                // checkpoint the state so we'll go to the next stage in case
320
                // of restarts.
321
                h.outputIncubating = true
4✔
322
                if err := h.Checkpoint(h); err != nil {
4✔
323
                        log.Errorf("unable to Checkpoint: %v", err)
×
324
                        return nil, err
×
325
                }
×
326

327
                log.Infof("%T(%x): second-level HTLC success transaction "+
4✔
328
                        "confirmed!", h, h.htlc.RHash[:])
4✔
329
        }
330

331
        // If we ended up here after a restart, we must again get the
332
        // spend notification.
333
        if commitSpend == nil {
6✔
334
                var err error
1✔
335
                commitSpend, err = waitForSpend(
1✔
336
                        &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
1✔
337
                        h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
1✔
338
                        h.broadcastHeight, h.Notifier, h.quit,
1✔
339
                )
1✔
340
                if err != nil {
1✔
341
                        return nil, err
×
342
                }
×
343
        }
344

345
        // The HTLC success tx has a CSV lock that we must wait for, and if
346
        // this is a lease enforced channel and we're the imitator, we may need
347
        // to wait for longer.
348
        waitHeight := h.deriveWaitHeight(
5✔
349
                h.htlcResolution.CsvDelay, commitSpend,
5✔
350
        )
5✔
351

5✔
352
        // Now that the sweeper has broadcasted the second-level transaction,
5✔
353
        // it has confirmed, and we have checkpointed our state, we'll sweep
5✔
354
        // the second level output. We report the resolver has moved the next
5✔
355
        // stage.
5✔
356
        h.reportLock.Lock()
5✔
357
        h.currentReport.Stage = 2
5✔
358
        h.currentReport.MaturityHeight = waitHeight
5✔
359
        h.reportLock.Unlock()
5✔
360

5✔
361
        if h.hasCLTV() {
8✔
362
                log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
3✔
363
                        "expire at height %v", h, h.htlc.RHash[:],
3✔
364
                        waitHeight)
3✔
365
        } else {
8✔
366
                log.Infof("%T(%x): waiting for CSV lock to expire at "+
5✔
367
                        "height %v", h, h.htlc.RHash[:], waitHeight)
5✔
368
        }
5✔
369

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

5✔
388
        // TODO(yy): let sweeper handles the wait?
5✔
389
        err := waitForHeight(waitHeight, h.Notifier, h.quit)
5✔
390
        if err != nil {
8✔
391
                return nil, err
3✔
392
        }
3✔
393

394
        // We'll use this input index to determine the second-level output
395
        // index on the transaction, as the signatures requires the indexes to
396
        // be the same. We don't look for the second-level output script
397
        // directly, as there might be more than one HTLC output to the same
398
        // pkScript.
399
        op := &wire.OutPoint{
5✔
400
                Hash:  *commitSpend.SpenderTxHash,
5✔
401
                Index: commitSpend.SpenderInputIndex,
5✔
402
        }
5✔
403

5✔
404
        // Let the sweeper sweep the second-level output now that the
5✔
405
        // CSV/CLTV locks have expired.
5✔
406
        var witType input.StandardWitnessType
5✔
407
        if isTaproot {
8✔
408
                witType = input.TaprootHtlcAcceptedSuccessSecondLevel
3✔
409
        } else {
8✔
410
                witType = input.HtlcAcceptedSuccessSecondLevel
5✔
411
        }
5✔
412
        inp := h.makeSweepInput(
5✔
413
                op, witType,
5✔
414
                input.LeaseHtlcAcceptedSuccessSecondLevel,
5✔
415
                &h.htlcResolution.SweepSignDesc,
5✔
416
                h.htlcResolution.CsvDelay, uint32(commitSpend.SpendingHeight),
5✔
417
                h.htlc.RHash,
5✔
418
        )
5✔
419

5✔
420
        // Calculate the budget for this sweep.
5✔
421
        budget := calculateBudget(
5✔
422
                btcutil.Amount(inp.SignDesc().Output.Value),
5✔
423
                h.Budget.NoDeadlineHTLCRatio,
5✔
424
                h.Budget.NoDeadlineHTLC,
5✔
425
        )
5✔
426

5✔
427
        log.Infof("%T(%x): offering second-level success tx output to sweeper "+
5✔
428
                "with no deadline and budget=%v at height=%v", h,
5✔
429
                h.htlc.RHash[:], budget, waitHeight)
5✔
430

5✔
431
        // TODO(roasbeef): need to update above for leased types
5✔
432
        _, err = h.Sweeper.SweepInput(
5✔
433
                inp,
5✔
434
                sweep.Params{
5✔
435
                        Budget: budget,
5✔
436

5✔
437
                        // For second level success tx, there's no rush to get
5✔
438
                        // it confirmed, so we use a nil deadline.
5✔
439
                        DeadlineHeight: fn.None[int32](),
5✔
440
                },
5✔
441
        )
5✔
442
        if err != nil {
5✔
443
                return nil, err
×
444
        }
×
445

446
        // Will return this outpoint, when this is spent the resolver is fully
447
        // resolved.
448
        return op, nil
5✔
449
}
450

451
// resolveRemoteCommitOutput handles sweeping an HTLC output on the remote
452
// commitment with the preimage. In this case we can sweep the output directly,
453
// and don't have to broadcast a second-level transaction.
454
func (h *htlcSuccessResolver) resolveRemoteCommitOutput(immediate bool) (
455
        ContractResolver, error) {
4✔
456

4✔
457
        isTaproot := txscript.IsPayToTaproot(
4✔
458
                h.htlcResolution.SweepSignDesc.Output.PkScript,
4✔
459
        )
4✔
460

4✔
461
        // Before we can craft out sweeping transaction, we need to
4✔
462
        // create an input which contains all the items required to add
4✔
463
        // this input to a sweeping transaction, and generate a
4✔
464
        // witness.
4✔
465
        var inp input.Input
4✔
466
        if isTaproot {
7✔
467
                inp = lnutils.Ptr(input.MakeTaprootHtlcSucceedInput(
3✔
468
                        &h.htlcResolution.ClaimOutpoint,
3✔
469
                        &h.htlcResolution.SweepSignDesc,
3✔
470
                        h.htlcResolution.Preimage[:],
3✔
471
                        h.broadcastHeight,
3✔
472
                        h.htlcResolution.CsvDelay,
3✔
473
                ))
3✔
474
        } else {
7✔
475
                inp = lnutils.Ptr(input.MakeHtlcSucceedInput(
4✔
476
                        &h.htlcResolution.ClaimOutpoint,
4✔
477
                        &h.htlcResolution.SweepSignDesc,
4✔
478
                        h.htlcResolution.Preimage[:],
4✔
479
                        h.broadcastHeight,
4✔
480
                        h.htlcResolution.CsvDelay,
4✔
481
                ))
4✔
482
        }
4✔
483

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

4✔
491
        deadline := fn.Some(int32(h.htlc.RefundTimeout))
4✔
492

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

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

510
        // Wait for the direct-preimage HTLC sweep tx to confirm.
511
        sweepTxDetails, err := waitForSpend(
4✔
512
                &h.htlcResolution.ClaimOutpoint,
4✔
513
                h.htlcResolution.SweepSignDesc.Output.PkScript,
4✔
514
                h.broadcastHeight, h.Notifier, h.quit,
4✔
515
        )
4✔
516
        if err != nil {
6✔
517
                return nil, err
2✔
518
        }
2✔
519

520
        // Once the transaction has received a sufficient number of
521
        // confirmations, we'll mark ourselves as fully resolved and exit.
522
        h.resolved = true
4✔
523

4✔
524
        // Checkpoint the resolver, and write the outcome to disk.
4✔
525
        return nil, h.checkpointClaim(
4✔
526
                sweepTxDetails.SpenderTxHash,
4✔
527
                channeldb.ResolverOutcomeClaimed,
4✔
528
        )
4✔
529
}
530

531
// checkpointClaim checkpoints the success resolver with the reports it needs.
532
// If this htlc was claimed two stages, it will write reports for both stages,
533
// otherwise it will just write for the single htlc claim.
534
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
535
        outcome channeldb.ResolverOutcome) error {
8✔
536

8✔
537
        // Mark the htlc as final settled.
8✔
538
        err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
8✔
539
                h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
8✔
540
        )
8✔
541
        if err != nil {
8✔
542
                return err
×
543
        }
×
544

545
        // Send notification.
546
        h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
8✔
547
                models.CircuitKey{
8✔
548
                        ChanID: h.ShortChanID,
8✔
549
                        HtlcID: h.htlc.HtlcIndex,
8✔
550
                },
8✔
551
                channeldb.FinalHtlcInfo{
8✔
552
                        Settled:  true,
8✔
553
                        Offchain: false,
8✔
554
                },
8✔
555
        )
8✔
556

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

8✔
569
        // If we have a success tx, we append a report to represent our first
8✔
570
        // stage claim.
8✔
571
        if h.htlcResolution.SignedSuccessTx != nil {
15✔
572
                // If the SignedSuccessTx is not nil, we are claiming the htlc
7✔
573
                // in two stages, so we need to create a report for the first
7✔
574
                // stage transaction as well.
7✔
575
                spendTx := h.htlcResolution.SignedSuccessTx
7✔
576
                spendTxID := spendTx.TxHash()
7✔
577

7✔
578
                report := &channeldb.ResolverReport{
7✔
579
                        OutPoint:        spendTx.TxIn[0].PreviousOutPoint,
7✔
580
                        Amount:          h.htlc.Amt.ToSatoshis(),
7✔
581
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
7✔
582
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
7✔
583
                        SpendTxID:       &spendTxID,
7✔
584
                }
7✔
585
                reports = append(reports, report)
7✔
586
        }
7✔
587

588
        // Finally, we checkpoint the resolver with our report(s).
589
        return h.Checkpoint(h, reports...)
8✔
590
}
591

592
// Stop signals the resolver to cancel any current resolution processes, and
593
// suspend.
594
//
595
// NOTE: Part of the ContractResolver interface.
596
func (h *htlcSuccessResolver) Stop() {
3✔
597
        close(h.quit)
3✔
598
}
3✔
599

600
// IsResolved returns true if the stored state in the resolve is fully
601
// resolved. In this case the target output can be forgotten.
602
//
603
// NOTE: Part of the ContractResolver interface.
604
func (h *htlcSuccessResolver) IsResolved() bool {
3✔
605
        return h.resolved
3✔
606
}
3✔
607

608
// report returns a report on the resolution state of the contract.
609
func (h *htlcSuccessResolver) report() *ContractReport {
3✔
610
        // If the sign details are nil, the report will be created by handled
3✔
611
        // by the nursery.
3✔
612
        if h.htlcResolution.SignDetails == nil {
6✔
613
                return nil
3✔
614
        }
3✔
615

616
        h.reportLock.Lock()
3✔
617
        defer h.reportLock.Unlock()
3✔
618
        cpy := h.currentReport
3✔
619
        return &cpy
3✔
620
}
621

622
func (h *htlcSuccessResolver) initReport() {
10✔
623
        // We create the initial report. This will only be reported for
10✔
624
        // resolvers not handled by the nursery.
10✔
625
        finalAmt := h.htlc.Amt.ToSatoshis()
10✔
626
        if h.htlcResolution.SignedSuccessTx != nil {
17✔
627
                finalAmt = btcutil.Amount(
7✔
628
                        h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
7✔
629
                )
7✔
630
        }
7✔
631

632
        h.currentReport = ContractReport{
10✔
633
                Outpoint:       h.htlcResolution.ClaimOutpoint,
10✔
634
                Type:           ReportOutputIncomingHtlc,
10✔
635
                Amount:         finalAmt,
10✔
636
                MaturityHeight: h.htlcResolution.CsvDelay,
10✔
637
                LimboBalance:   finalAmt,
10✔
638
                Stage:          1,
10✔
639
        }
10✔
640
}
641

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

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

667
        // We encode the sign details last for backwards compatibility.
668
        err := encodeSignDetails(w, h.htlcResolution.SignDetails)
12✔
669
        if err != nil {
12✔
670
                return err
×
671
        }
×
672

673
        return nil
12✔
674
}
675

676
// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
677
// from the passed Reader instance, returning an active ContractResolver
678
// instance.
679
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
680
        *htlcSuccessResolver, error) {
10✔
681

10✔
682
        h := &htlcSuccessResolver{
10✔
683
                contractResolverKit: *newContractResolverKit(resCfg),
10✔
684
        }
10✔
685

10✔
686
        // First we'll decode our inner HTLC resolution.
10✔
687
        if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
10✔
688
                return nil, err
×
689
        }
×
690

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

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

716
        h.initReport()
10✔
717

10✔
718
        return h, nil
10✔
719
}
720

721
// Supplement adds additional information to the resolver that is required
722
// before Resolve() is called.
723
//
724
// NOTE: Part of the htlcContractResolver interface.
725
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
5✔
726
        h.htlc = htlc
5✔
727
}
5✔
728

729
// HtlcPoint returns the htlc's outpoint on the commitment tx.
730
//
731
// NOTE: Part of the htlcContractResolver interface.
732
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
3✔
733
        return h.htlcResolution.HtlcPoint()
3✔
734
}
3✔
735

736
// SupplementDeadline does nothing for an incoming htlc resolver.
737
//
738
// NOTE: Part of the htlcContractResolver interface.
739
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
×
740
}
×
741

742
// A compile time assertion to ensure htlcSuccessResolver meets the
743
// ContractResolver interface.
744
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