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

lightningnetwork / lnd / 12115442155

02 Dec 2024 08:28AM UTC coverage: 48.662% (-10.3%) from 58.948%
12115442155

Pull #9175

github

ellemouton
netann: update ChanAnn2 validation to work for P2WSH channels

This commit expands the ChannelAnnouncement2 validation for the case
where it is announcing a P2WSH channel.
Pull Request #9175: lnwire+netann: update structure of g175 messages to be pure TLV

6 of 314 new or added lines in 9 files covered. (1.91%)

27532 existing lines in 434 files now uncovered.

97890 of 201164 relevant lines covered (48.66%)

0.52 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 {
1✔
75

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

1✔
83
        h.initReport()
1✔
84

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

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

1✔
121
        // If we're already resolved, then we can exit early.
1✔
122
        if h.resolved {
1✔
UNCOV
123
                return nil, nil
×
UNCOV
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 {
2✔
129
                return h.resolveRemoteCommitOutput(immediate)
1✔
130
        }
1✔
131

132
        // Otherwise this an output on our own commitment, and we must start by
133
        // broadcasting the second-level success transaction.
134
        secondLevelOutpoint, err := h.broadcastSuccessTx(immediate)
1✔
135
        if err != nil {
2✔
136
                return nil, err
1✔
137
        }
1✔
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 "+
1✔
142
                "after csv_delay=%v", h, h.htlc.RHash[:], h.htlcResolution.CsvDelay)
1✔
143

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

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

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

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

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

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

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

UNCOV
214
                h.outputIncubating = true
×
UNCOV
215

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

UNCOV
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) {
1✔
232

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

505
        // Wait for the direct-preimage HTLC sweep tx to confirm.
506
        sweepTxDetails, err := waitForSpend(
1✔
507
                &h.htlcResolution.ClaimOutpoint,
1✔
508
                h.htlcResolution.SweepSignDesc.Output.PkScript,
1✔
509
                h.broadcastHeight, h.Notifier, h.quit,
1✔
510
        )
1✔
511
        if err != nil {
2✔
512
                return nil, err
1✔
513
        }
1✔
514

515
        // Once the transaction has received a sufficient number of
516
        // confirmations, we'll mark ourselves as fully resolved and exit.
517
        h.resolved = true
1✔
518

1✔
519
        // Checkpoint the resolver, and write the outcome to disk.
1✔
520
        return nil, h.checkpointClaim(
1✔
521
                sweepTxDetails.SpenderTxHash,
1✔
522
                channeldb.ResolverOutcomeClaimed,
1✔
523
        )
1✔
524
}
525

526
// checkpointClaim checkpoints the success resolver with the reports it needs.
527
// If this htlc was claimed two stages, it will write reports for both stages,
528
// otherwise it will just write for the single htlc claim.
529
func (h *htlcSuccessResolver) checkpointClaim(spendTx *chainhash.Hash,
530
        outcome channeldb.ResolverOutcome) error {
1✔
531

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

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

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

1✔
564
        // If we have a success tx, we append a report to represent our first
1✔
565
        // stage claim.
1✔
566
        if h.htlcResolution.SignedSuccessTx != nil {
2✔
567
                // If the SignedSuccessTx is not nil, we are claiming the htlc
1✔
568
                // in two stages, so we need to create a report for the first
1✔
569
                // stage transaction as well.
1✔
570
                spendTx := h.htlcResolution.SignedSuccessTx
1✔
571
                spendTxID := spendTx.TxHash()
1✔
572

1✔
573
                report := &channeldb.ResolverReport{
1✔
574
                        OutPoint:        spendTx.TxIn[0].PreviousOutPoint,
1✔
575
                        Amount:          h.htlc.Amt.ToSatoshis(),
1✔
576
                        ResolverType:    channeldb.ResolverTypeIncomingHtlc,
1✔
577
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
1✔
578
                        SpendTxID:       &spendTxID,
1✔
579
                }
1✔
580
                reports = append(reports, report)
1✔
581
        }
1✔
582

583
        // Finally, we checkpoint the resolver with our report(s).
584
        return h.Checkpoint(h, reports...)
1✔
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() {
1✔
592
        close(h.quit)
1✔
593
}
1✔
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 {
1✔
600
        return h.resolved
1✔
601
}
1✔
602

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

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

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

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

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

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

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

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

711
        h.initReport()
1✔
712

1✔
713
        return h, nil
1✔
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.
UNCOV
720
func (h *htlcSuccessResolver) Supplement(htlc channeldb.HTLC) {
×
UNCOV
721
        h.htlc = htlc
×
UNCOV
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 {
1✔
728
        return h.htlcResolution.HtlcPoint()
1✔
729
}
1✔
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