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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

77.43
/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,
UNCOV
74
        resCfg ResolverConfig) *htlcSuccessResolver {
×
UNCOV
75

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

×
UNCOV
83
        h.initReport()
×
UNCOV
84

×
UNCOV
85
        return h
×
UNCOV
86
}
×
87

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

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

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

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

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

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

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

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

4✔
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✔
UNCOV
161
        )
×
UNCOV
162
}
×
163

164
// broadcastSuccessTx handles an HTLC output on our local commitment by
4✔
165
// broadcasting the second-level success transaction. It returns the ultimate
4✔
166
// outpoint of the second-level tx, that we must wait to be spent for the
4✔
167
// resolver to be fully resolved.
4✔
168
func (h *htlcSuccessResolver) broadcastSuccessTx(
4✔
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
174
        // and attach fees at will. We let the sweeper handle this job.  We use
175
        // the checkpointed outputIncubating field to determine if we already
176
        // swept the HTLC output into the second level transaction.
177
        if h.htlcResolution.SignDetails != nil {
178
                return h.broadcastReSignedSuccessTx(immediate)
179
        }
180

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

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

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

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

1✔
214
                h.outputIncubating = true
1✔
215

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

1✔
UNCOV
222
        return &h.htlcResolution.ClaimOutpoint, nil
×
UNCOV
223
}
×
224

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2✔
359
        // Deduct one block so this input is offered to the sweeper one block
2✔
360
        // earlier since the sweeper will wait for one block to trigger the
2✔
361
        // sweeping.
2✔
UNCOV
362
        //
×
UNCOV
363
        // TODO(yy): this is done so the outputs can be aggregated
×
UNCOV
364
        // properly. Suppose CSV locks of five 2nd-level outputs all
×
365
        // expire at height 840000, there is a race in block digestion
2✔
366
        // between contractcourt and sweeper:
2✔
367
        // - G1: block 840000 received in contractcourt, it now offers
2✔
368
        //   the outputs to the sweeper.
2✔
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--
376

377
        // TODO(yy): let sweeper handles the wait?
378
        err := waitForHeight(waitHeight, h.Notifier, h.quit)
379
        if err != nil {
380
                return nil, err
381
        }
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
2✔
387
        // pkScript.
2✔
388
        op := &wire.OutPoint{
2✔
389
                Hash:  *commitSpend.SpenderTxHash,
2✔
390
                Index: commitSpend.SpenderInputIndex,
2✔
UNCOV
391
        }
×
UNCOV
392

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
499
        // Wait for the direct-preimage HTLC sweep tx to confirm.
1✔
500
        sweepTxDetails, err := waitForSpend(
1✔
501
                &h.htlcResolution.ClaimOutpoint,
1✔
502
                h.htlcResolution.SweepSignDesc.Output.PkScript,
1✔
503
                h.broadcastHeight, h.Notifier, h.quit,
1✔
504
        )
1✔
505
        if err != nil {
1✔
506
                return nil, err
1✔
UNCOV
507
        }
×
UNCOV
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
1✔
512

1✔
513
        // Checkpoint the resolver, and write the outcome to disk.
1✔
514
        return nil, h.checkpointClaim(
1✔
515
                sweepTxDetails.SpenderTxHash,
1✔
516
                channeldb.ResolverOutcomeClaimed,
1✔
UNCOV
517
        )
×
UNCOV
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.
1✔
523
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
1✔
524
        outcome channeldb.ResolverOutcome) error {
1✔
525

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

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

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

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

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

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

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

589
// IsResolved returns true if the stored state in the resolve is fully
5✔
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 {
594
        return h.resolved
595
}
UNCOV
596

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

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

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

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

4✔
631
// Encode writes an encoded version of the ContractResolver into the passed
632
// Writer.
7✔
633
//
7✔
634
// NOTE: Part of the ContractResolver interface.
7✔
635
func (h *htlcSuccessResolver) Encode(w io.Writer) error {
7✔
636
        // First we'll encode our inner HTLC resolution.
7✔
637
        if err := encodeIncomingResolution(w, &h.htlcResolution); err != nil {
7✔
638
                return err
7✔
639
        }
7✔
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 {
644
                return err
645
        }
646
        if err := binary.Write(w, endian, h.resolved); err != nil {
9✔
647
                return err
9✔
648
        }
9✔
UNCOV
649
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
×
650
                return err
×
651
        }
652
        if _, err := w.Write(h.htlc.RHash[:]); err != nil {
653
                return err
654
        }
9✔
UNCOV
655

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

×
UNCOV
662
        return nil
×
663
}
9✔
UNCOV
664

×
UNCOV
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) (
9✔
669
        *htlcSuccessResolver, error) {
9✔
UNCOV
670

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

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

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

×
UNCOV
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
7✔
UNCOV
697
        // it, but don't error out if there are not bytes left.
×
UNCOV
698
        signDetails, err := decodeSignDetails(r)
×
699
        if err == nil {
7✔
UNCOV
700
                h.htlcResolution.SignDetails = signDetails
×
UNCOV
701
        } else if err != io.EOF && err != io.ErrUnexpectedEOF {
×
702
                return nil, err
7✔
703
        }
×
UNCOV
704

×
705
        h.initReport()
706

707
        return h, nil
708
}
709

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

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

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

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