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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

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

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

3✔
121
        // If we're already resolved, then we can exit early.
3✔
122
        if h.resolved {
3✔
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 {
6✔
129
                return h.resolveRemoteCommitOutput(immediate)
3✔
130
        }
3✔
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)
3✔
135
        if err != nil {
6✔
136
                return nil, err
3✔
137
        }
3✔
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 "+
3✔
142
                "after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
3✔
143

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

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

3✔
158
        h.resolved = true
3✔
159
        return nil, h.checkpointClaim(
3✔
160
                spend.SpenderTxHash, channeldb.ResolverOutcomeClaimed,
3✔
161
        )
3✔
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) {
3✔
170

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

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

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

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

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

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

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

294
                log.Infof("%T(%x): waiting for second-level HTLC success "+
3✔
295
                        "transaction to confirm", h, h.htlc.RHash[:])
3✔
296

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

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

316
                log.Infof("%T(%x): second-level HTLC success transaction "+
3✔
317
                        "confirmed!", h, h.htlc.RHash[:])
3✔
318
        }
319

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

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

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

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

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

3✔
377
        // TODO(yy): let sweeper handles the wait?
3✔
378
        err := waitForHeight(waitHeight, h.Notifier, h.quit)
3✔
379
        if err != nil {
6✔
380
                return nil, err
3✔
381
        }
3✔
382

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

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

3✔
409
        // Calculate the budget for this sweep.
3✔
410
        budget := calculateBudget(
3✔
411
                btcutil.Amount(inp.SignDesc().Output.Value),
3✔
412
                h.Budget.NoDeadlineHTLCRatio,
3✔
413
                h.Budget.NoDeadlineHTLC,
3✔
414
        )
3✔
415

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

3✔
420
        // TODO(roasbeef): need to update above for leased types
3✔
421
        _, err = h.Sweeper.SweepInput(
3✔
422
                inp,
3✔
423
                sweep.Params{
3✔
424
                        Budget: budget,
3✔
425

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

435
        // Will return this outpoint, when this is spent the resolver is fully
436
        // resolved.
437
        return op, nil
3✔
438
}
439

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

3✔
446
        isTaproot := txscript.IsPayToTaproot(
3✔
447
                h.htlcResolution.SweepSignDesc.Output.PkScript,
3✔
448
        )
3✔
449

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

473
        // Calculate the budget for this sweep.
474
        budget := calculateBudget(
3✔
475
                btcutil.Amount(inp.SignDesc().Output.Value),
3✔
476
                h.Budget.DeadlineHTLCRatio,
3✔
477
                h.Budget.DeadlineHTLC,
3✔
478
        )
3✔
479

3✔
480
        deadline := fn.Some(int32(h.htlc.RefundTimeout))
3✔
481

3✔
482
        log.Infof("%T(%x): offering direct-preimage HTLC output to sweeper "+
3✔
483
                "with deadline=%v, budget=%v", h, h.htlc.RHash[:],
3✔
484
                h.htlc.RefundTimeout, budget)
3✔
485

3✔
486
        // We'll now offer the direct preimage HTLC to the sweeper.
3✔
487
        _, err := h.Sweeper.SweepInput(
3✔
488
                inp,
3✔
489
                sweep.Params{
3✔
490
                        Budget:         budget,
3✔
491
                        DeadlineHeight: deadline,
3✔
492
                        Immediate:      immediate,
3✔
493
                },
3✔
494
        )
3✔
495
        if err != nil {
3✔
496
                return nil, err
×
497
        }
×
498

499
        // Wait for the direct-preimage HTLC sweep tx to confirm.
500
        sweepTxDetails, err := waitForSpend(
3✔
501
                &h.htlcResolution.ClaimOutpoint,
3✔
502
                h.htlcResolution.SweepSignDesc.Output.PkScript,
3✔
503
                h.broadcastHeight, h.Notifier, h.quit,
3✔
504
        )
3✔
505
        if err != nil {
6✔
506
                return nil, err
3✔
507
        }
3✔
508

509
        // Once the transaction has received a sufficient number of
510
        // confirmations, we'll mark ourselves as fully resolved and exit.
511
        h.resolved = true
3✔
512

3✔
513
        // Checkpoint the resolver, and write the outcome to disk.
3✔
514
        return nil, h.checkpointClaim(
3✔
515
                sweepTxDetails.SpenderTxHash,
3✔
516
                channeldb.ResolverOutcomeClaimed,
3✔
517
        )
3✔
518
}
519

520
// checkpointClaim checkpoints the success resolver with the reports it needs.
521
// If this htlc was claimed two stages, it will write reports for both stages,
522
// otherwise it will just write for the single htlc claim.
523
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
524
        outcome channeldb.ResolverOutcome) error {
3✔
525

3✔
526
        // Mark the htlc as final settled.
3✔
527
        err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
3✔
528
                h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, true,
3✔
529
        )
3✔
530
        if err != nil {
3✔
531
                return err
×
532
        }
×
533

534
        // Send notification.
535
        h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
3✔
536
                models.CircuitKey{
3✔
537
                        ChanID: h.ShortChanID,
3✔
538
                        HtlcID: h.htlc.HtlcIndex,
3✔
539
                },
3✔
540
                channeldb.FinalHtlcInfo{
3✔
541
                        Settled:  true,
3✔
542
                        Offchain: false,
3✔
543
                },
3✔
544
        )
3✔
545

3✔
546
        // Create a resolver report for claiming of the htlc itself.
3✔
547
        amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
3✔
548
        reports := []*channeldb.ResolverReport{
3✔
549
                {
3✔
550
                        OutPoint:        h.htlcResolution.ClaimOutpoint,
3✔
551
                        Amount:          amt,
3✔
552
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
3✔
553
                        ResolverOutcome: outcome,
3✔
554
                        SpendTxID:       spendTx,
3✔
555
                },
3✔
556
        }
3✔
557

3✔
558
        // If we have a success tx, we append a report to represent our first
3✔
559
        // stage claim.
3✔
560
        if h.htlcResolution.SignedSuccessTx != nil {
6✔
561
                // If the SignedSuccessTx is not nil, we are claiming the htlc
3✔
562
                // in two stages, so we need to create a report for the first
3✔
563
                // stage transaction as well.
3✔
564
                spendTx := h.htlcResolution.SignedSuccessTx
3✔
565
                spendTxID := spendTx.TxHash()
3✔
566

3✔
567
                report := &channeldb.ResolverReport{
3✔
568
                        OutPoint:        spendTx.TxIn[0].PreviousOutPoint,
3✔
569
                        Amount:          h.htlc.Amt.ToSatoshis(),
3✔
570
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
3✔
571
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
3✔
572
                        SpendTxID:       &spendTxID,
3✔
573
                }
3✔
574
                reports = append(reports, report)
3✔
575
        }
3✔
576

577
        // Finally, we checkpoint the resolver with our report(s).
578
        return h.Checkpoint(h, reports...)
3✔
579
}
580

581
// Stop signals the resolver to cancel any current resolution processes, and
582
// suspend.
583
//
584
// NOTE: Part of the ContractResolver interface.
585
func (h *htlcSuccessResolver) Stop() {
3✔
586
        close(h.quit)
3✔
587
}
3✔
588

589
// IsResolved returns true if the stored state in the resolve is fully
590
// resolved. In this case the target output can be forgotten.
591
//
592
// NOTE: Part of the ContractResolver interface.
593
func (h *htlcSuccessResolver) IsResolved() bool {
3✔
594
        return h.resolved
3✔
595
}
3✔
596

597
// report returns a report on the resolution state of the contract.
598
func (h *htlcSuccessResolver) report() *ContractReport {
3✔
599
        // If the sign details are nil, the report will be created by handled
3✔
600
        // by the nursery.
3✔
601
        if h.htlcResolution.SignDetails == nil {
6✔
602
                return nil
3✔
603
        }
3✔
604

605
        h.reportLock.Lock()
3✔
606
        defer h.reportLock.Unlock()
3✔
607
        cpy := h.currentReport
3✔
608
        return &cpy
3✔
609
}
610

611
func (h *htlcSuccessResolver) initReport() {
3✔
612
        // We create the initial report. This will only be reported for
3✔
613
        // resolvers not handled by the nursery.
3✔
614
        finalAmt := h.htlc.Amt.ToSatoshis()
3✔
615
        if h.htlcResolution.SignedSuccessTx != nil {
6✔
616
                finalAmt = btcutil.Amount(
3✔
617
                        h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
3✔
618
                )
3✔
619
        }
3✔
620

621
        h.currentReport = ContractReport{
3✔
622
                Outpoint:       h.htlcResolution.ClaimOutpoint,
3✔
623
                Type:           ReportOutputIncomingHtlc,
3✔
624
                Amount:         finalAmt,
3✔
625
                MaturityHeight: h.htlcResolution.CsvDelay,
3✔
626
                LimboBalance:   finalAmt,
3✔
627
                Stage:          1,
3✔
628
        }
3✔
629
}
630

631
// Encode writes an encoded version of the ContractResolver into the passed
632
// Writer.
633
//
634
// NOTE: Part of the ContractResolver interface.
635
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
3✔
636
        // First we'll encode our inner HTLC resolution.
3✔
637
        if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
3✔
638
                return err
×
639
        }
×
640

641
        // Next, we'll write out the fields that are specified to the contract
642
        // resolver.
643
        if err := binary.Write(w, endian, h.outputIncubating); err != nil {
3✔
644
                return err
×
645
        }
×
646
        if err := binary.Write(w, endian, h.resolved); err != nil {
3✔
647
                return err
×
648
        }
×
649
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
3✔
650
                return err
×
651
        }
×
652
        if _, err := w.Write(h.htlc.RHash[:]); err != nil {
3✔
653
                return err
×
654
        }
×
655

656
        // We encode the sign details last for backwards compatibility.
657
        err := encodeSignDetails(w, h.htlcResolution.SignDetails)
3✔
658
        if err != nil {
3✔
659
                return err
×
660
        }
×
661

662
        return nil
3✔
663
}
664

665
// newSuccessResolverFromReader attempts to decode an encoded ContractResolver
666
// from the passed Reader instance, returning an active ContractResolver
667
// instance.
668
func newSuccessResolverFromReader(r io.Reader, resCfg ResolverConfig) (
669
        *htlcSuccessResolver, error) {
3✔
670

3✔
671
        h := &htlcSuccessResolver{
3✔
672
                contractResolverKit: *newContractResolverKit(resCfg),
3✔
673
        }
3✔
674

3✔
675
        // First we'll decode our inner HTLC resolution.
3✔
676
        if err := decodeIncomingResolution(r, &h.htlcResolution); err != nil {
3✔
677
                return nil, err
×
678
        }
×
679

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

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

705
        h.initReport()
3✔
706

3✔
707
        return h, nil
3✔
708
}
709

710
// Supplement adds additional information to the resolver that is required
711
// before Resolve() is called.
712
//
713
// NOTE: Part of the htlcContractResolver interface.
714
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
×
715
        h.htlc = htlc
×
716
}
×
717

718
// HtlcPoint returns the htlc's outpoint on the commitment tx.
719
//
720
// NOTE: Part of the htlcContractResolver interface.
721
func (h *htlcSuccessResolver) HtlcPoint() wire.OutPoint {
3✔
722
        return h.htlcResolution.HtlcPoint()
3✔
723
}
3✔
724

725
// SupplementDeadline does nothing for an incoming htlc resolver.
726
//
727
// NOTE: Part of the htlcContractResolver interface.
728
func (h *htlcSuccessResolver) SupplementDeadline(_ fn.Option[int32]) {
×
729
}
×
730

731
// A compile time assertion to ensure htlcSuccessResolver meets the
732
// ContractResolver interface.
733
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