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

lightningnetwork / lnd / 12199391122

06 Dec 2024 01:10PM UTC coverage: 49.807% (-9.1%) from 58.933%
12199391122

push

github

web-flow
Merge pull request #9337 from Guayaba221/patch-1

chore: fix typo in ruby.md

100137 of 201051 relevant lines covered (49.81%)

2.07 hits per line

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

82.27
/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"
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 {
4✔
75

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

4✔
83
        h.initReport()
4✔
84

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

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

4✔
121
        // If we're already resolved, then we can exit early.
4✔
122
        if h.resolved {
4✔
123
                return nil, nil
×
124
        }
×
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 {
8✔
129
                return h.resolveRemoteCommitOutput(immediate)
4✔
130
        }
4✔
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 {
8✔
136
                return nil, err
4✔
137
        }
4✔
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 {
8✔
178
                return h.broadcastReSignedSuccessTx(immediate)
4✔
179
        }
4✔
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",
×
184
                h, h.htlc.RHash[:], spew.Sdump(h.htlcResolution.SignedSuccessTx))
×
185

×
186
        // We'll now broadcast the second layer transaction so we can kick off
×
187
        // the claiming process.
×
188
        //
×
189
        // TODO(roasbeef): after changing sighashes send to tx bundler
×
190
        label := labels.MakeLabel(
×
191
                labels.LabelTypeChannelClose, &h.ShortChanID,
×
192
        )
×
193
        err := h.PublishTx(h.htlcResolution.SignedSuccessTx, label)
×
194
        if err != nil {
×
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 {
×
202
                log.Infof("%T(%x): incubating incoming htlc output",
×
203
                        h, h.htlc.RHash[:])
×
204

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

214
                h.outputIncubating = true
×
215

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

222
        return &h.htlcResolution.ClaimOutpoint, nil
×
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) {
4✔
232

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

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

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

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

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

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

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

4✔
300
                // Wait for the second level transaction to confirm.
4✔
301
                commitSpend, err = waitForSpend(
4✔
302
                        &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
4✔
303
                        h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
4✔
304
                        h.broadcastHeight, h.Notifier, h.quit,
4✔
305
                )
4✔
306
                if err != nil {
8✔
307
                        return nil, err
4✔
308
                }
4✔
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
4✔
314
                if err := h.Checkpoint(h); err != nil {
4✔
315
                        log.Errorf("unable to Checkpoint: %v", err)
×
316
                        return nil, err
×
317
                }
×
318

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

323
        // If we ended up here after a restart, we must again get the
324
        // spend notification.
325
        if commitSpend == nil {
4✔
326
                var err error
×
327
                commitSpend, err = waitForSpend(
×
328
                        &h.htlcResolution.SignedSuccessTx.TxIn[0].PreviousOutPoint,
×
329
                        h.htlcResolution.SignDetails.SignDesc.Output.PkScript,
×
330
                        h.broadcastHeight, h.Notifier, h.quit,
×
331
                )
×
332
                if err != nil {
×
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(
4✔
341
                h.htlcResolution.CsvDelay, commitSpend,
4✔
342
        )
4✔
343

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

4✔
353
        if h.hasCLTV() {
8✔
354
                log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
4✔
355
                        "expire at height %v", h, h.htlc.RHash[:],
4✔
356
                        waitHeight)
4✔
357
        } else {
8✔
358
                log.Infof("%T(%x): waiting for CSV lock to expire at "+
4✔
359
                        "height %v", h, h.htlc.RHash[:], waitHeight)
4✔
360
        }
4✔
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--
4✔
379

4✔
380
        // TODO(yy): let sweeper handles the wait?
4✔
381
        err := waitForHeight(waitHeight, h.Notifier, h.quit)
4✔
382
        if err != nil {
8✔
383
                return nil, err
4✔
384
        }
4✔
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{
4✔
392
                Hash:  *commitSpend.SpenderTxHash,
4✔
393
                Index: commitSpend.SpenderInputIndex,
4✔
394
        }
4✔
395

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

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

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

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

4✔
429
                        // For second level success tx, there's no rush to get
4✔
430
                        // it confirmed, so we use a nil deadline.
4✔
431
                        DeadlineHeight: fn.None[int32](),
4✔
432
                },
4✔
433
        )
4✔
434
        if err != nil {
4✔
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
4✔
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) {
4✔
448

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

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

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

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

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

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

505
        // Wait for the direct-preimage HTLC sweep tx to confirm.
506
        sweepTxDetails, err := waitForSpend(
4✔
507
                &h.htlcResolution.ClaimOutpoint,
4✔
508
                h.htlcResolution.SweepSignDesc.Output.PkScript,
4✔
509
                h.broadcastHeight, h.Notifier, h.quit,
4✔
510
        )
4✔
511
        if err != nil {
7✔
512
                return nil, err
3✔
513
        }
3✔
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
4✔
518

4✔
519
        // Checkpoint the resolver, and write the outcome to disk.
4✔
520
        return nil, h.checkpointClaim(
4✔
521
                sweepTxDetails.SpenderTxHash,
4✔
522
                channeldb.ResolverOutcomeClaimed,
4✔
523
        )
4✔
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 {
4✔
531

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

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

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

4✔
564
        // If we have a success tx, we append a report to represent our first
4✔
565
        // stage claim.
4✔
566
        if h.htlcResolution.SignedSuccessTx != nil {
8✔
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...)
4✔
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() {
4✔
592
        close(h.quit)
4✔
593
}
4✔
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 {
4✔
600
        return h.resolved
4✔
601
}
4✔
602

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

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

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

627
        h.currentReport = ContractReport{
4✔
628
                Outpoint:       h.htlcResolution.ClaimOutpoint,
4✔
629
                Type:           ReportOutputIncomingHtlc,
4✔
630
                Amount:         finalAmt,
4✔
631
                MaturityHeight: h.htlcResolution.CsvDelay,
4✔
632
                LimboBalance:   finalAmt,
4✔
633
                Stage:          1,
4✔
634
        }
4✔
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 {
4✔
642
        // First we'll encode our inner HTLC resolution.
4✔
643
        if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
4✔
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 {
4✔
650
                return err
×
651
        }
×
652
        if err := binary.Write(w, endian, h.resolved); err != nil {
4✔
653
                return err
×
654
        }
×
655
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
4✔
656
                return err
×
657
        }
×
658
        if _, err := w.Write(h.htlc.RHash[:]); err != nil {
4✔
659
                return err
×
660
        }
×
661

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

668
        return nil
4✔
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) {
4✔
676

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

4✔
681
        // First we'll decode our inner HTLC resolution.
4✔
682
        if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
4✔
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 {
4✔
689
                return nil, err
×
690
        }
×
691
        if err := binary.Read(r, endian, &h.resolved); err != nil {
4✔
692
                return nil, err
×
693
        }
×
694
        if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
4✔
695
                return nil, err
×
696
        }
×
697
        if _, err := io.ReadFull(r, h.htlc.RHash[:]); err != nil {
4✔
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)
4✔
705
        if err == nil {
8✔
706
                h.htlcResolution.SignDetails = signDetails
4✔
707
        } else if err != io.EOF && err != io.ErrUnexpectedEOF {
4✔
708
                return nil, err
×
709
        }
×
710

711
        h.initReport()
4✔
712

4✔
713
        return h, nil
4✔
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) {
×
721
        h.htlc = htlc
×
722
}
×
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 {
4✔
728
        return h.htlcResolution.HtlcPoint()
4✔
729
}
4✔
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