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

lightningnetwork / lnd / 12312390362

13 Dec 2024 08:44AM UTC coverage: 57.458% (+8.5%) from 48.92%
12312390362

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

In this commit, the ContextGuard struct is re-worked such that the
context that its new main WithCtx method provides is cancelled in sync
with a parent context being cancelled or with it's quit channel being
cancelled. Tests are added to assert the behaviour. In order for the
close of the quit channel to be consistent with the cancelling of the
derived context, the quit channel _must_ be contained internal to the
ContextGuard so that callers are only able to close the channel via the
exposed Quit method which will then take care to first cancel any
derived context that depend on the quit channel before returning.
Pull Request #9343: fn: expand the ContextGuard and add tests

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

67.28
/contractcourt/htlc_timeout_resolver.go
1
package contractcourt
2

3
import (
4
        "encoding/binary"
5
        "fmt"
6
        "io"
7
        "sync"
8

9
        "github.com/btcsuite/btcd/btcutil"
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/v2"
16
        "github.com/lightningnetwork/lnd/input"
17
        "github.com/lightningnetwork/lnd/lntypes"
18
        "github.com/lightningnetwork/lnd/lnutils"
19
        "github.com/lightningnetwork/lnd/lnwallet"
20
        "github.com/lightningnetwork/lnd/lnwire"
21
        "github.com/lightningnetwork/lnd/sweep"
22
)
23

24
// htlcTimeoutResolver is a ContractResolver that's capable of resolving an
25
// outgoing HTLC. The HTLC may be on our commitment transaction, or on the
26
// commitment transaction of the remote party. An output on our commitment
27
// transaction is considered fully resolved once the second-level transaction
28
// has been confirmed (and reached a sufficient depth). An output on the
29
// commitment transaction of the remote party is resolved once we detect a
30
// spend of the direct HTLC output using the timeout clause.
31
type htlcTimeoutResolver struct {
32
        // htlcResolution contains all the information required to properly
33
        // resolve this outgoing HTLC.
34
        htlcResolution lnwallet.OutgoingHtlcResolution
35

36
        // outputIncubating returns true if we've sent the output to the output
37
        // incubator (utxo nursery).
38
        outputIncubating bool
39

40
        // resolved reflects if the contract has been fully resolved or not.
41
        resolved bool
42

43
        // broadcastHeight is the height that the original contract was
44
        // broadcast to the main-chain at. We'll use this value to bound any
45
        // historical queries to the chain for spends/confirmations.
46
        //
47
        // TODO(roasbeef): wrap above into definite resolution embedding?
48
        broadcastHeight uint32
49

50
        // htlc contains information on the htlc that we are resolving on-chain.
51
        htlc channeldb.HTLC
52

53
        // currentReport stores the current state of the resolver for reporting
54
        // over the rpc interface. This should only be reported in case we have
55
        // a non-nil SignDetails on the htlcResolution, otherwise the nursery
56
        // will produce reports.
57
        currentReport ContractReport
58

59
        // reportLock prevents concurrent access to the resolver report.
60
        reportLock sync.Mutex
61

62
        contractResolverKit
63

64
        htlcLeaseResolver
65

66
        // incomingHTLCExpiryHeight is the absolute block height at which the
67
        // incoming HTLC will expire. This is used as the deadline height as
68
        // the outgoing HTLC must be swept before its incoming HTLC expires.
69
        incomingHTLCExpiryHeight fn.Option[int32]
70
}
71

72
// newTimeoutResolver instantiates a new timeout htlc resolver.
73
func newTimeoutResolver(res lnwallet.OutgoingHtlcResolution,
74
        broadcastHeight uint32, htlc channeldb.HTLC,
75
        resCfg ResolverConfig) *htlcTimeoutResolver {
1✔
76

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

1✔
84
        h.initReport()
1✔
85

1✔
86
        return h
1✔
87
}
1✔
88

89
// isTaproot returns true if the htlc output is a taproot output.
90
func (h *htlcTimeoutResolver) isTaproot() bool {
57✔
91
        return txscript.IsPayToTaproot(
57✔
92
                h.htlcResolution.SweepSignDesc.Output.PkScript,
57✔
93
        )
57✔
94
}
57✔
95

96
// ResolverKey returns an identifier which should be globally unique for this
97
// particular resolver within the chain the original contract resides within.
98
//
99
// NOTE: Part of the ContractResolver interface.
100
func (h *htlcTimeoutResolver) ResolverKey() []byte {
19✔
101
        // The primary key for this resolver will be the outpoint of the HTLC
19✔
102
        // on the commitment transaction itself. If this is our commitment,
19✔
103
        // then the output can be found within the signed timeout tx,
19✔
104
        // otherwise, it's just the ClaimOutpoint.
19✔
105
        var op wire.OutPoint
19✔
106
        if h.htlcResolution.SignedTimeoutTx != nil {
27✔
107
                op = h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
8✔
108
        } else {
19✔
109
                op = h.htlcResolution.ClaimOutpoint
11✔
110
        }
11✔
111

112
        key := newResolverID(op)
19✔
113
        return key[:]
19✔
114
}
115

116
const (
117
        // expectedRemoteWitnessSuccessSize is the expected size of the witness
118
        // on the remote commitment transaction for an outgoing HTLC that is
119
        // swept on-chain by them with pre-image.
120
        expectedRemoteWitnessSuccessSize = 5
121

122
        // expectedLocalWitnessSuccessSize is the expected size of the witness
123
        // on the local commitment transaction for an outgoing HTLC that is
124
        // swept on-chain by them with pre-image.
125
        expectedLocalWitnessSuccessSize = 3
126

127
        // remotePreimageIndex index within the witness on the remote
128
        // commitment transaction that will hold they pre-image if they go to
129
        // sweep it on chain.
130
        remotePreimageIndex = 3
131

132
        // localPreimageIndex is the index within the witness on the local
133
        // commitment transaction for an outgoing HTLC that will hold the
134
        // pre-image if the remote party sweeps it.
135
        localPreimageIndex = 1
136

137
        // remoteTaprootWitnessSuccessSize is the expected size of the witness
138
        // on the remote commitment for taproot channels. The spend path will
139
        // look like
140
        //   - <sender sig> <receiver sig> <preimage> <success_script>
141
        //     <control_block>
142
        remoteTaprootWitnessSuccessSize = 5
143

144
        // localTaprootWitnessSuccessSize is the expected size of the witness
145
        // on the local commitment for taproot channels. The spend path will
146
        // look like
147
        //  - <receiver sig> <preimage> <success_script> <control_block>
148
        localTaprootWitnessSuccessSize = 4
149

150
        // taprootRemotePreimageIndex is the index within the witness on the
151
        // taproot remote commitment spend that'll hold the pre-image if the
152
        // remote party sweeps it.
153
        taprootRemotePreimageIndex = 2
154
)
155

156
// claimCleanUp is a helper method that's called once the HTLC output is spent
157
// by the remote party. It'll extract the preimage, add it to the global cache,
158
// and finally send the appropriate clean up message.
159
func (h *htlcTimeoutResolver) claimCleanUp(
160
        commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
8✔
161

8✔
162
        // Depending on if this is our commitment or not, then we'll be looking
8✔
163
        // for a different witness pattern.
8✔
164
        spenderIndex := commitSpend.SpenderInputIndex
8✔
165
        spendingInput := commitSpend.SpendingTx.TxIn[spenderIndex]
8✔
166

8✔
167
        log.Infof("%T(%v): extracting preimage! remote party spent "+
8✔
168
                "HTLC with tx=%v", h, h.htlcResolution.ClaimOutpoint,
8✔
169
                spew.Sdump(commitSpend.SpendingTx))
8✔
170

8✔
171
        // If this is the remote party's commitment, then we'll be looking for
8✔
172
        // them to spend using the second-level success transaction.
8✔
173
        var preimageBytes []byte
8✔
174
        switch {
8✔
175
        // For taproot channels, if the remote party has swept the HTLC, then
176
        // the witness stack will look like:
177
        //
178
        //   - <sender sig> <receiver sig> <preimage> <success_script>
179
        //     <control_block>
180
        case h.isTaproot() && h.htlcResolution.SignedTimeoutTx == nil:
×
181
                //nolint:ll
×
182
                preimageBytes = spendingInput.Witness[taprootRemotePreimageIndex]
×
183

184
        // The witness stack when the remote party sweeps the output on a
185
        // regular channel to them looks like:
186
        //
187
        //  - <0> <sender sig> <recvr sig> <preimage> <witness script>
188
        case !h.isTaproot() && h.htlcResolution.SignedTimeoutTx == nil:
3✔
189
                preimageBytes = spendingInput.Witness[remotePreimageIndex]
3✔
190

191
        // If this is a taproot channel, and there's only a single witness
192
        // element, then we're actually on the losing side of a breach
193
        // attempt...
194
        case h.isTaproot() && len(spendingInput.Witness) == 1:
×
195
                return nil, fmt.Errorf("breach attempt failed")
×
196

197
        // Otherwise, they'll be spending directly from our commitment output.
198
        // In which case the witness stack looks like:
199
        //
200
        //  - <sig> <preimage> <witness script>
201
        //
202
        // For taproot channels, this looks like:
203
        //  - <receiver sig> <preimage> <success_script> <control_block>
204
        //
205
        // So we can target the same index.
206
        default:
5✔
207
                preimageBytes = spendingInput.Witness[localPreimageIndex]
5✔
208
        }
209

210
        preimage, err := lntypes.MakePreimage(preimageBytes)
8✔
211
        if err != nil {
8✔
212
                return nil, fmt.Errorf("unable to create pre-image from "+
×
213
                        "witness: %v", err)
×
214
        }
×
215

216
        log.Infof("%T(%v): extracting preimage=%v from on-chain "+
8✔
217
                "spend!", h, h.htlcResolution.ClaimOutpoint, preimage)
8✔
218

8✔
219
        // With the preimage obtained, we can now add it to the global cache.
8✔
220
        if err := h.PreimageDB.AddPreimages(preimage); err != nil {
8✔
221
                log.Errorf("%T(%v): unable to add witness to cache",
×
222
                        h, h.htlcResolution.ClaimOutpoint)
×
223
        }
×
224

225
        var pre [32]byte
8✔
226
        copy(pre[:], preimage[:])
8✔
227

8✔
228
        // Finally, we'll send the clean up message, mark ourselves as
8✔
229
        // resolved, then exit.
8✔
230
        if err := h.DeliverResolutionMsg(ResolutionMsg{
8✔
231
                SourceChan: h.ShortChanID,
8✔
232
                HtlcIndex:  h.htlc.HtlcIndex,
8✔
233
                PreImage:   &pre,
8✔
234
        }); err != nil {
8✔
235
                return nil, err
×
236
        }
×
237
        h.resolved = true
8✔
238

8✔
239
        // Checkpoint our resolver with a report which reflects the preimage
8✔
240
        // claim by the remote party.
8✔
241
        amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
8✔
242
        report := &channeldb.ResolverReport{
8✔
243
                OutPoint:        h.htlcResolution.ClaimOutpoint,
8✔
244
                Amount:          amt,
8✔
245
                ResolverType:    channeldb.ResolverTypeOutgoingHtlc,
8✔
246
                ResolverOutcome: channeldb.ResolverOutcomeClaimed,
8✔
247
                SpendTxID:       commitSpend.SpenderTxHash,
8✔
248
        }
8✔
249

8✔
250
        return nil, h.Checkpoint(h, report)
8✔
251
}
252

253
// chainDetailsToWatch returns the output and script which we use to watch for
254
// spends from the direct HTLC output on the commitment transaction.
255
func (h *htlcTimeoutResolver) chainDetailsToWatch() (*wire.OutPoint, []byte, error) {
19✔
256
        // If there's no timeout transaction, it means we are spending from a
19✔
257
        // remote commit, then the claim output is the output directly on the
19✔
258
        // commitment transaction, so we'll just use that.
19✔
259
        if h.htlcResolution.SignedTimeoutTx == nil {
25✔
260
                outPointToWatch := h.htlcResolution.ClaimOutpoint
6✔
261
                scriptToWatch := h.htlcResolution.SweepSignDesc.Output.PkScript
6✔
262

6✔
263
                return &outPointToWatch, scriptToWatch, nil
6✔
264
        }
6✔
265

266
        // If SignedTimeoutTx is not nil, this is the local party's commitment,
267
        // and we'll need to grab watch the output that our timeout transaction
268
        // points to. We can directly grab the outpoint, then also extract the
269
        // witness script (the last element of the witness stack) to
270
        // re-construct the pkScript we need to watch.
271
        //
272
        //nolint:ll
273
        outPointToWatch := h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
13✔
274
        witness := h.htlcResolution.SignedTimeoutTx.TxIn[0].Witness
13✔
275

13✔
276
        var (
13✔
277
                scriptToWatch []byte
13✔
278
                err           error
13✔
279
        )
13✔
280
        switch {
13✔
281
        // For taproot channels, then final witness element is the control
282
        // block, and the one before it the witness script. We can use both of
283
        // these together to reconstruct the taproot output key, then map that
284
        // into a v1 witness program.
285
        case h.isTaproot():
×
286
                // First, we'll parse the control block into something we can
×
287
                // use.
×
288
                ctrlBlockBytes := witness[len(witness)-1]
×
289
                ctrlBlock, err := txscript.ParseControlBlock(ctrlBlockBytes)
×
290
                if err != nil {
×
291
                        return nil, nil, err
×
292
                }
×
293

294
                // With the control block, we'll grab the witness script, then
295
                // use that to derive the tapscript root.
296
                witnessScript := witness[len(witness)-2]
×
297
                tapscriptRoot := ctrlBlock.RootHash(witnessScript)
×
298

×
299
                // Once we have the root, then we can derive the output key
×
300
                // from the internal key, then turn that into a witness
×
301
                // program.
×
302
                outputKey := txscript.ComputeTaprootOutputKey(
×
303
                        ctrlBlock.InternalKey, tapscriptRoot,
×
304
                )
×
305
                scriptToWatch, err = txscript.PayToTaprootScript(outputKey)
×
306
                if err != nil {
×
307
                        return nil, nil, err
×
308
                }
×
309

310
        // For regular channels, the witness script is the last element on the
311
        // stack. We can then use this to re-derive the output that we're
312
        // watching on chain.
313
        default:
13✔
314
                scriptToWatch, err = input.WitnessScriptHash(
13✔
315
                        witness[len(witness)-1],
13✔
316
                )
13✔
317
        }
318
        if err != nil {
13✔
319
                return nil, nil, err
×
320
        }
×
321

322
        return &outPointToWatch, scriptToWatch, nil
13✔
323
}
324

325
// isPreimageSpend returns true if the passed spend on the specified commitment
326
// is a success spend that reveals the pre-image or not.
327
func isPreimageSpend(isTaproot bool, spend *chainntnfs.SpendDetail,
328
        localCommit bool) bool {
19✔
329

19✔
330
        // Based on the spending input index and transaction, obtain the
19✔
331
        // witness that tells us what type of spend this is.
19✔
332
        spenderIndex := spend.SpenderInputIndex
19✔
333
        spendingInput := spend.SpendingTx.TxIn[spenderIndex]
19✔
334
        spendingWitness := spendingInput.Witness
19✔
335

19✔
336
        switch {
19✔
337
        // If this is a taproot remote commitment, then we can detect the type
338
        // of spend via the leaf revealed in the control block and the witness
339
        // itself.
340
        //
341
        // The keyspend (revocation path) is just a single signature, while the
342
        // timeout and success paths are most distinct.
343
        //
344
        // The success path will look like:
345
        //
346
        //   - <sender sig> <receiver sig> <preimage> <success_script>
347
        //     <control_block>
348
        case isTaproot && !localCommit:
1✔
349
                return checkSizeAndIndex(
1✔
350
                        spendingWitness, remoteTaprootWitnessSuccessSize,
1✔
351
                        taprootRemotePreimageIndex,
1✔
352
                )
1✔
353

354
        // Otherwise, then if this is our local commitment transaction, then if
355
        // they're sweeping the transaction, it'll be directly from the output,
356
        // skipping the second level.
357
        //
358
        // In this case, then there're two main tapscript paths, with the
359
        // success case look like:
360
        //
361
        //  - <receiver sig> <preimage> <success_script> <control_block>
362
        case isTaproot && localCommit:
1✔
363
                return checkSizeAndIndex(
1✔
364
                        spendingWitness, localTaprootWitnessSuccessSize,
1✔
365
                        localPreimageIndex,
1✔
366
                )
1✔
367

368
        // If this is the non-taproot, remote commitment then the only possible
369
        // spends for outgoing HTLCs are:
370
        //
371
        //  RECVR: <0> <sender sig> <recvr sig> <preimage> (2nd level success spend)
372
        //  REVOK: <sig> <key>
373
        //  SENDR: <sig> 0
374
        //
375
        // In this case, if 5 witness elements are present (factoring the
376
        // witness script), and the 3rd element is the size of the pre-image,
377
        // then this is a remote spend. If not, then we swept it ourselves, or
378
        // revoked their output.
379
        case !isTaproot && !localCommit:
5✔
380
                return checkSizeAndIndex(
5✔
381
                        spendingWitness, expectedRemoteWitnessSuccessSize,
5✔
382
                        remotePreimageIndex,
5✔
383
                )
5✔
384

385
        // Otherwise, for our non-taproot commitment, the only possible spends
386
        // for an outgoing HTLC are:
387
        //
388
        //  SENDR: <0> <sendr sig>  <recvr sig> <0> (2nd level timeout)
389
        //  RECVR: <recvr sig>  <preimage>
390
        //  REVOK: <revoke sig> <revoke key>
391
        //
392
        // So the only success case has the pre-image as the 2nd (index 1)
393
        // element in the witness.
394
        case !isTaproot:
12✔
395
                fallthrough
12✔
396

397
        default:
12✔
398
                return checkSizeAndIndex(
12✔
399
                        spendingWitness, expectedLocalWitnessSuccessSize,
12✔
400
                        localPreimageIndex,
12✔
401
                )
12✔
402
        }
403
}
404

405
// checkSizeAndIndex checks that the witness is of the expected size and that
406
// the witness element at the specified index is of the expected size.
407
func checkSizeAndIndex(witness wire.TxWitness, size, index int) bool {
22✔
408
        if len(witness) != size {
31✔
409
                return false
9✔
410
        }
9✔
411

412
        return len(witness[index]) == lntypes.HashSize
13✔
413
}
414

415
// Resolve kicks off full resolution of an outgoing HTLC output. If it's our
416
// commitment, it isn't resolved until we see the second level HTLC txn
417
// confirmed. If it's the remote party's commitment, we don't resolve until we
418
// see a direct sweep via the timeout clause.
419
//
420
// NOTE: Part of the ContractResolver interface.
421
func (h *htlcTimeoutResolver) Resolve(
422
        immediate bool) (ContractResolver, error) {
21✔
423

21✔
424
        // If we're already resolved, then we can exit early.
21✔
425
        if h.resolved {
27✔
426
                return nil, nil
6✔
427
        }
6✔
428

429
        // Start by spending the HTLC output, either by broadcasting the
430
        // second-level timeout transaction, or directly if this is the remote
431
        // commitment.
432
        commitSpend, err := h.spendHtlcOutput(immediate)
15✔
433
        if err != nil {
15✔
434
                return nil, err
×
435
        }
×
436

437
        // If the spend reveals the pre-image, then we'll enter the clean up
438
        // workflow to pass the pre-image back to the incoming link, add it to
439
        // the witness cache, and exit.
440
        if isPreimageSpend(
15✔
441
                h.isTaproot(), commitSpend,
15✔
442
                h.htlcResolution.SignedTimeoutTx != nil,
15✔
443
        ) {
22✔
444

7✔
445
                log.Infof("%T(%v): HTLC has been swept with pre-image by "+
7✔
446
                        "remote party during timeout flow! Adding pre-image to "+
7✔
447
                        "witness cache", h, h.htlc.RHash[:],
7✔
448
                        h.htlcResolution.ClaimOutpoint)
7✔
449

7✔
450
                return h.claimCleanUp(commitSpend)
7✔
451
        }
7✔
452

453
        // At this point, the second-level transaction is sufficiently
454
        // confirmed, or a transaction directly spending the output is.
455
        // Therefore, we can now send back our clean up message, failing the
456
        // HTLC on the incoming link.
457
        //
458
        // NOTE: This can be called twice if the outgoing resolver restarts
459
        // before the second-stage timeout transaction is confirmed.
460
        log.Infof("%T(%v): resolving htlc with incoming fail msg, "+
8✔
461
                "fully confirmed", h, h.htlcResolution.ClaimOutpoint)
8✔
462

8✔
463
        failureMsg := &lnwire.FailPermanentChannelFailure{}
8✔
464
        err = h.DeliverResolutionMsg(ResolutionMsg{
8✔
465
                SourceChan: h.ShortChanID,
8✔
466
                HtlcIndex:  h.htlc.HtlcIndex,
8✔
467
                Failure:    failureMsg,
8✔
468
        })
8✔
469
        if err != nil {
8✔
470
                return nil, err
×
471
        }
×
472

473
        // Depending on whether this was a local or remote commit, we must
474
        // handle the spending transaction accordingly.
475
        return h.handleCommitSpend(commitSpend)
8✔
476
}
477

478
// sweepSecondLevelTx sends a second level timeout transaction to the sweeper.
479
// This transaction uses the SINLGE|ANYONECANPAY flag.
480
func (h *htlcTimeoutResolver) sweepSecondLevelTx(immediate bool) error {
2✔
481
        log.Infof("%T(%x): offering second-layer timeout tx to sweeper: %v",
2✔
482
                h, h.htlc.RHash[:],
2✔
483
                spew.Sdump(h.htlcResolution.SignedTimeoutTx))
2✔
484

2✔
485
        var inp input.Input
2✔
486
        if h.isTaproot() {
2✔
487
                inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutTaprootInput(
×
488
                        h.htlcResolution.SignedTimeoutTx,
×
489
                        h.htlcResolution.SignDetails,
×
490
                        h.broadcastHeight,
×
491
                        input.WithResolutionBlob(
×
492
                                h.htlcResolution.ResolutionBlob,
×
493
                        ),
×
494
                ))
×
495
        } else {
2✔
496
                inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutAnchorInput(
2✔
497
                        h.htlcResolution.SignedTimeoutTx,
2✔
498
                        h.htlcResolution.SignDetails,
2✔
499
                        h.broadcastHeight,
2✔
500
                ))
2✔
501
        }
2✔
502

503
        // Calculate the budget.
504
        //
505
        // TODO(yy): the budget is twice the output's value, which is needed as
506
        // we don't force sweep the output now. To prevent cascading force
507
        // closes, we use all its output value plus a wallet input as the
508
        // budget. This is a temporary solution until we can optionally cancel
509
        // the incoming HTLC, more details in,
510
        // - https://github.com/lightningnetwork/lnd/issues/7969
511
        budget := calculateBudget(
2✔
512
                btcutil.Amount(inp.SignDesc().Output.Value), 2, 0,
2✔
513
        )
2✔
514

2✔
515
        // For an outgoing HTLC, it must be swept before the RefundTimeout of
2✔
516
        // its incoming HTLC is reached.
2✔
517
        //
2✔
518
        // TODO(yy): we may end up mixing inputs with different time locks.
2✔
519
        // Suppose we have two outgoing HTLCs,
2✔
520
        // - HTLC1: nLocktime is 800000, CLTV delta is 80.
2✔
521
        // - HTLC2: nLocktime is 800001, CLTV delta is 79.
2✔
522
        // This means they would both have an incoming HTLC that expires at
2✔
523
        // 800080, hence they share the same deadline but different locktimes.
2✔
524
        // However, with current design, when we are at block 800000, HTLC1 is
2✔
525
        // offered to the sweeper. When block 800001 is reached, HTLC1's
2✔
526
        // sweeping process is already started, while HTLC2 is being offered to
2✔
527
        // the sweeper, so they won't be mixed. This can become an issue tho,
2✔
528
        // if we decide to sweep per X blocks. Or the contractcourt sees the
2✔
529
        // block first while the sweeper is only aware of the last block. To
2✔
530
        // properly fix it, we need `blockbeat` to make sure subsystems are in
2✔
531
        // sync.
2✔
532
        log.Infof("%T(%x): offering second-level HTLC timeout tx to sweeper "+
2✔
533
                "with deadline=%v, budget=%v", h, h.htlc.RHash[:],
2✔
534
                h.incomingHTLCExpiryHeight, budget)
2✔
535

2✔
536
        _, err := h.Sweeper.SweepInput(
2✔
537
                inp,
2✔
538
                sweep.Params{
2✔
539
                        Budget:         budget,
2✔
540
                        DeadlineHeight: h.incomingHTLCExpiryHeight,
2✔
541
                        Immediate:      immediate,
2✔
542
                },
2✔
543
        )
2✔
544
        if err != nil {
2✔
545
                return err
×
546
        }
×
547

548
        return err
2✔
549
}
550

551
// sendSecondLevelTxLegacy sends a second level timeout transaction to the utxo
552
// nursery. This transaction uses the legacy SIGHASH_ALL flag.
553
func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error {
5✔
554
        log.Debugf("%T(%v): incubating htlc output", h,
5✔
555
                h.htlcResolution.ClaimOutpoint)
5✔
556

5✔
557
        err := h.IncubateOutputs(
5✔
558
                h.ChanPoint, fn.Some(h.htlcResolution),
5✔
559
                fn.None[lnwallet.IncomingHtlcResolution](),
5✔
560
                h.broadcastHeight, h.incomingHTLCExpiryHeight,
5✔
561
        )
5✔
562
        if err != nil {
5✔
563
                return err
×
564
        }
×
565

566
        h.outputIncubating = true
5✔
567

5✔
568
        return h.Checkpoint(h)
5✔
569
}
570

571
// sweepDirectHtlcOutput sends the direct spend of the HTLC output to the
572
// sweeper. This is used when the remote party goes on chain, and we're able to
573
// sweep an HTLC we offered after a timeout. Only the CLTV encumbered outputs
574
// are resolved via this path.
575
func (h *htlcTimeoutResolver) sweepDirectHtlcOutput(immediate bool) error {
4✔
576
        var htlcWitnessType input.StandardWitnessType
4✔
577
        if h.isTaproot() {
4✔
578
                htlcWitnessType = input.TaprootHtlcOfferedRemoteTimeout
×
579
        } else {
4✔
580
                htlcWitnessType = input.HtlcOfferedRemoteTimeout
4✔
581
        }
4✔
582

583
        sweepInput := input.NewCsvInputWithCltv(
4✔
584
                &h.htlcResolution.ClaimOutpoint, htlcWitnessType,
4✔
585
                &h.htlcResolution.SweepSignDesc, h.broadcastHeight,
4✔
586
                h.htlcResolution.CsvDelay, h.htlcResolution.Expiry,
4✔
587
                input.WithResolutionBlob(h.htlcResolution.ResolutionBlob),
4✔
588
        )
4✔
589

4✔
590
        // Calculate the budget.
4✔
591
        //
4✔
592
        // TODO(yy): the budget is twice the output's value, which is needed as
4✔
593
        // we don't force sweep the output now. To prevent cascading force
4✔
594
        // closes, we use all its output value plus a wallet input as the
4✔
595
        // budget. This is a temporary solution until we can optionally cancel
4✔
596
        // the incoming HTLC, more details in,
4✔
597
        // - https://github.com/lightningnetwork/lnd/issues/7969
4✔
598
        budget := calculateBudget(
4✔
599
                btcutil.Amount(sweepInput.SignDesc().Output.Value), 2, 0,
4✔
600
        )
4✔
601

4✔
602
        log.Infof("%T(%x): offering offered remote timeout HTLC output to "+
4✔
603
                "sweeper with deadline %v and budget=%v at height=%v",
4✔
604
                h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget,
4✔
605
                h.broadcastHeight)
4✔
606

4✔
607
        _, err := h.Sweeper.SweepInput(
4✔
608
                sweepInput,
4✔
609
                sweep.Params{
4✔
610
                        Budget: budget,
4✔
611

4✔
612
                        // This is an outgoing HTLC, so we want to make sure
4✔
613
                        // that we sweep it before the incoming HTLC expires.
4✔
614
                        DeadlineHeight: h.incomingHTLCExpiryHeight,
4✔
615
                        Immediate:      immediate,
4✔
616
                },
4✔
617
        )
4✔
618
        if err != nil {
4✔
619
                return err
×
620
        }
×
621

622
        return nil
4✔
623
}
624

625
// spendHtlcOutput handles the initial spend of an HTLC output via the timeout
626
// clause. If this is our local commitment, the second-level timeout TX will be
627
// used to spend the output into the next stage. If this is the remote
628
// commitment, the output will be swept directly without the timeout
629
// transaction.
630
func (h *htlcTimeoutResolver) spendHtlcOutput(
631
        immediate bool) (*chainntnfs.SpendDetail, error) {
15✔
632

15✔
633
        switch {
15✔
634
        // If we have non-nil SignDetails, this means that have a 2nd level
635
        // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY
636
        // (the case for anchor type channels). In this case we can re-sign it
637
        // and attach fees at will. We let the sweeper handle this job.
638
        case h.htlcResolution.SignDetails != nil && !h.outputIncubating:
2✔
639
                if err := h.sweepSecondLevelTx(immediate); err != nil {
2✔
640
                        log.Errorf("Sending timeout tx to sweeper: %v", err)
×
641

×
642
                        return nil, err
×
643
                }
×
644

645
        // If this is a remote commitment there's no second level timeout txn,
646
        // and we can just send this directly to the sweeper.
647
        case h.htlcResolution.SignedTimeoutTx == nil && !h.outputIncubating:
4✔
648
                if err := h.sweepDirectHtlcOutput(immediate); err != nil {
4✔
649
                        log.Errorf("Sending direct spend to sweeper: %v", err)
×
650

×
651
                        return nil, err
×
652
                }
×
653

654
        // If we have a SignedTimeoutTx but no SignDetails, this is a local
655
        // commitment for a non-anchor channel, so we'll send it to the utxo
656
        // nursery.
657
        case h.htlcResolution.SignDetails == nil && !h.outputIncubating:
5✔
658
                if err := h.sendSecondLevelTxLegacy(); err != nil {
5✔
659
                        log.Errorf("Sending timeout tx to nursery: %v", err)
×
660

×
661
                        return nil, err
×
662
                }
×
663
        }
664

665
        // Now that we've handed off the HTLC to the nursery or sweeper, we'll
666
        // watch for a spend of the output, and make our next move off of that.
667
        // Depending on if this is our commitment, or the remote party's
668
        // commitment, we'll be watching a different outpoint and script.
669
        return h.watchHtlcSpend()
15✔
670
}
671

672
// watchHtlcSpend watches for a spend of the HTLC output. For neutrino backend,
673
// it will check blocks for the confirmed spend. For btcd and bitcoind, it will
674
// check both the mempool and the blocks.
675
func (h *htlcTimeoutResolver) watchHtlcSpend() (*chainntnfs.SpendDetail,
676
        error) {
15✔
677

15✔
678
        // TODO(yy): outpointToWatch is always h.HtlcOutpoint(), can refactor
15✔
679
        // to remove the redundancy.
15✔
680
        outpointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
15✔
681
        if err != nil {
15✔
682
                return nil, err
×
683
        }
×
684

685
        // If there's no mempool configured, which is the case for SPV node
686
        // such as neutrino, then we will watch for confirmed spend only.
687
        if h.Mempool == nil {
30✔
688
                return h.waitForConfirmedSpend(outpointToWatch, scriptToWatch)
15✔
689
        }
15✔
690

691
        // Watch for a spend of the HTLC output in both the mempool and blocks.
692
        return h.waitForMempoolOrBlockSpend(*outpointToWatch, scriptToWatch)
×
693
}
694

695
// waitForConfirmedSpend waits for the HTLC output to be spent and confirmed in
696
// a block, returns the spend details.
697
func (h *htlcTimeoutResolver) waitForConfirmedSpend(op *wire.OutPoint,
698
        pkScript []byte) (*chainntnfs.SpendDetail, error) {
15✔
699

15✔
700
        log.Infof("%T(%v): waiting for spent of HTLC output %v to be "+
15✔
701
                "fully confirmed", h, h.htlcResolution.ClaimOutpoint, op)
15✔
702

15✔
703
        // We'll block here until either we exit, or the HTLC output on the
15✔
704
        // commitment transaction has been spent.
15✔
705
        spend, err := waitForSpend(
15✔
706
                op, pkScript, h.broadcastHeight, h.Notifier, h.quit,
15✔
707
        )
15✔
708
        if err != nil {
15✔
709
                return nil, err
×
710
        }
×
711

712
        // Once confirmed, persist the state on disk.
713
        if err := h.checkPointSecondLevelTx(); err != nil {
15✔
714
                return nil, err
×
715
        }
×
716

717
        return spend, err
15✔
718
}
719

720
// checkPointSecondLevelTx persists the state of a second level HTLC tx to disk
721
// if it's published by the sweeper.
722
func (h *htlcTimeoutResolver) checkPointSecondLevelTx() error {
15✔
723
        // If this was the second level transaction published by the sweeper,
15✔
724
        // we can checkpoint the resolver now that it's confirmed.
15✔
725
        if h.htlcResolution.SignDetails != nil && !h.outputIncubating {
17✔
726
                h.outputIncubating = true
2✔
727
                if err := h.Checkpoint(h); err != nil {
2✔
728
                        log.Errorf("unable to Checkpoint: %v", err)
×
729
                        return err
×
730
                }
×
731
        }
732

733
        return nil
15✔
734
}
735

736
// handleCommitSpend handles the spend of the HTLC output on the commitment
737
// transaction. If this was our local commitment, the spend will be he
738
// confirmed second-level timeout transaction, and we'll sweep that into our
739
// wallet. If the was a remote commitment, the resolver will resolve
740
// immetiately.
741
func (h *htlcTimeoutResolver) handleCommitSpend(
742
        commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
8✔
743

8✔
744
        var (
8✔
745
                // claimOutpoint will be the outpoint of the second level
8✔
746
                // transaction, or on the remote commitment directly. It will
8✔
747
                // start out as set in the resolution, but we'll update it if
8✔
748
                // the second-level goes through the sweeper and changes its
8✔
749
                // txid.
8✔
750
                claimOutpoint = h.htlcResolution.ClaimOutpoint
8✔
751

8✔
752
                // spendTxID will be the ultimate spend of the claimOutpoint.
8✔
753
                // We set it to the commit spend for now, as this is the
8✔
754
                // ultimate spend in case this is a remote commitment. If we go
8✔
755
                // through the second-level transaction, we'll update this
8✔
756
                // accordingly.
8✔
757
                spendTxID = commitSpend.SpenderTxHash
8✔
758

8✔
759
                reports []*channeldb.ResolverReport
8✔
760
        )
8✔
761

8✔
762
        switch {
8✔
763

764
        // If we swept an HTLC directly off the remote party's commitment
765
        // transaction, then we can exit here as there's no second level sweep
766
        // to do.
767
        case h.htlcResolution.SignedTimeoutTx == nil:
2✔
768
                break
2✔
769

770
        // If the sweeper is handling the second level transaction, wait for
771
        // the CSV and possible CLTV lock to expire, before sweeping the output
772
        // on the second-level.
773
        case h.htlcResolution.SignDetails != nil:
2✔
774
                waitHeight := h.deriveWaitHeight(
2✔
775
                        h.htlcResolution.CsvDelay, commitSpend,
2✔
776
                )
2✔
777

2✔
778
                h.reportLock.Lock()
2✔
779
                h.currentReport.Stage = 2
2✔
780
                h.currentReport.MaturityHeight = waitHeight
2✔
781
                h.reportLock.Unlock()
2✔
782

2✔
783
                if h.hasCLTV() {
2✔
784
                        log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
×
785
                                "expire at height %v", h, h.htlc.RHash[:],
×
786
                                waitHeight)
×
787
                } else {
2✔
788
                        log.Infof("%T(%x): waiting for CSV lock to expire at "+
2✔
789
                                "height %v", h, h.htlc.RHash[:], waitHeight)
2✔
790
                }
2✔
791

792
                // Deduct one block so this input is offered to the sweeper one
793
                // block earlier since the sweeper will wait for one block to
794
                // trigger the sweeping.
795
                //
796
                // TODO(yy): this is done so the outputs can be aggregated
797
                // properly. Suppose CSV locks of five 2nd-level outputs all
798
                // expire at height 840000, there is a race in block digestion
799
                // between contractcourt and sweeper:
800
                // - G1: block 840000 received in contractcourt, it now offers
801
                //   the outputs to the sweeper.
802
                // - G2: block 840000 received in sweeper, it now starts to
803
                //   sweep the received outputs - there's no guarantee all
804
                //   fives have been received.
805
                // To solve this, we either offer the outputs earlier, or
806
                // implement `blockbeat`, and force contractcourt and sweeper
807
                // to consume each block sequentially.
808
                waitHeight--
2✔
809

2✔
810
                // TODO(yy): let sweeper handles the wait?
2✔
811
                err := waitForHeight(waitHeight, h.Notifier, h.quit)
2✔
812
                if err != nil {
2✔
813
                        return nil, err
×
814
                }
×
815

816
                // We'll use this input index to determine the second-level
817
                // output index on the transaction, as the signatures requires
818
                // the indexes to be the same. We don't look for the
819
                // second-level output script directly, as there might be more
820
                // than one HTLC output to the same pkScript.
821
                op := &wire.OutPoint{
2✔
822
                        Hash:  *commitSpend.SpenderTxHash,
2✔
823
                        Index: commitSpend.SpenderInputIndex,
2✔
824
                }
2✔
825

2✔
826
                var csvWitnessType input.StandardWitnessType
2✔
827
                if h.isTaproot() {
2✔
828
                        //nolint:ll
×
829
                        csvWitnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
×
830
                } else {
2✔
831
                        csvWitnessType = input.HtlcOfferedTimeoutSecondLevel
2✔
832
                }
2✔
833

834
                // Let the sweeper sweep the second-level output now that the
835
                // CSV/CLTV locks have expired.
836
                inp := h.makeSweepInput(
2✔
837
                        op, csvWitnessType,
2✔
838
                        input.LeaseHtlcOfferedTimeoutSecondLevel,
2✔
839
                        &h.htlcResolution.SweepSignDesc,
2✔
840
                        h.htlcResolution.CsvDelay,
2✔
841
                        uint32(commitSpend.SpendingHeight), h.htlc.RHash,
2✔
842
                        h.htlcResolution.ResolutionBlob,
2✔
843
                )
2✔
844

2✔
845
                // Calculate the budget for this sweep.
2✔
846
                budget := calculateBudget(
2✔
847
                        btcutil.Amount(inp.SignDesc().Output.Value),
2✔
848
                        h.Budget.NoDeadlineHTLCRatio,
2✔
849
                        h.Budget.NoDeadlineHTLC,
2✔
850
                )
2✔
851

2✔
852
                log.Infof("%T(%x): offering second-level timeout tx output to "+
2✔
853
                        "sweeper with no deadline and budget=%v at height=%v",
2✔
854
                        h, h.htlc.RHash[:], budget, waitHeight)
2✔
855

2✔
856
                _, err = h.Sweeper.SweepInput(
2✔
857
                        inp,
2✔
858
                        sweep.Params{
2✔
859
                                Budget: budget,
2✔
860

2✔
861
                                // For second level success tx, there's no rush
2✔
862
                                // to get it confirmed, so we use a nil
2✔
863
                                // deadline.
2✔
864
                                DeadlineHeight: fn.None[int32](),
2✔
865
                        },
2✔
866
                )
2✔
867
                if err != nil {
2✔
868
                        return nil, err
×
869
                }
×
870

871
                // Update the claim outpoint to point to the second-level
872
                // transaction created by the sweeper.
873
                claimOutpoint = *op
2✔
874
                fallthrough
2✔
875

876
        // Finally, if this was an output on our commitment transaction, we'll
877
        // wait for the second-level HTLC output to be spent, and for that
878
        // transaction itself to confirm.
879
        case h.htlcResolution.SignedTimeoutTx != nil:
6✔
880
                log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
6✔
881
                        "delayed output", h, claimOutpoint)
6✔
882

6✔
883
                sweepTx, err := waitForSpend(
6✔
884
                        &claimOutpoint,
6✔
885
                        h.htlcResolution.SweepSignDesc.Output.PkScript,
6✔
886
                        h.broadcastHeight, h.Notifier, h.quit,
6✔
887
                )
6✔
888
                if err != nil {
6✔
889
                        return nil, err
×
890
                }
×
891

892
                // Update the spend txid to the hash of the sweep transaction.
893
                spendTxID = sweepTx.SpenderTxHash
6✔
894

6✔
895
                // Once our sweep of the timeout tx has confirmed, we add a
6✔
896
                // resolution for our timeoutTx tx first stage transaction.
6✔
897
                timeoutTx := commitSpend.SpendingTx
6✔
898
                index := commitSpend.SpenderInputIndex
6✔
899
                spendHash := commitSpend.SpenderTxHash
6✔
900

6✔
901
                reports = append(reports, &channeldb.ResolverReport{
6✔
902
                        OutPoint:        timeoutTx.TxIn[index].PreviousOutPoint,
6✔
903
                        Amount:          h.htlc.Amt.ToSatoshis(),
6✔
904
                        ResolverType:    channeldb.ResolverTypeOutgoingHtlc,
6✔
905
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
6✔
906
                        SpendTxID:       spendHash,
6✔
907
                })
6✔
908
        }
909

910
        // With the clean up message sent, we'll now mark the contract
911
        // resolved, update the recovered balance, record the timeout and the
912
        // sweep txid on disk, and wait.
913
        h.resolved = true
8✔
914
        h.reportLock.Lock()
8✔
915
        h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
8✔
916
        h.currentReport.LimboBalance = 0
8✔
917
        h.reportLock.Unlock()
8✔
918

8✔
919
        amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
8✔
920
        reports = append(reports, &channeldb.ResolverReport{
8✔
921
                OutPoint:        claimOutpoint,
8✔
922
                Amount:          amt,
8✔
923
                ResolverType:    channeldb.ResolverTypeOutgoingHtlc,
8✔
924
                ResolverOutcome: channeldb.ResolverOutcomeTimeout,
8✔
925
                SpendTxID:       spendTxID,
8✔
926
        })
8✔
927

8✔
928
        return nil, h.Checkpoint(h, reports...)
8✔
929
}
930

931
// Stop signals the resolver to cancel any current resolution processes, and
932
// suspend.
933
//
934
// NOTE: Part of the ContractResolver interface.
935
func (h *htlcTimeoutResolver) Stop() {
1✔
936
        close(h.quit)
1✔
937
}
1✔
938

939
// IsResolved returns true if the stored state in the resolve is fully
940
// resolved. In this case the target output can be forgotten.
941
//
942
// NOTE: Part of the ContractResolver interface.
943
func (h *htlcTimeoutResolver) IsResolved() bool {
3✔
944
        return h.resolved
3✔
945
}
3✔
946

947
// report returns a report on the resolution state of the contract.
948
func (h *htlcTimeoutResolver) report() *ContractReport {
×
949
        // If we have a SignedTimeoutTx but no SignDetails, this is a local
×
950
        // commitment for a non-anchor channel, which was handled by the utxo
×
951
        // nursery.
×
952
        if h.htlcResolution.SignDetails == nil && h.
×
953
                htlcResolution.SignedTimeoutTx != nil {
×
954
                return nil
×
955
        }
×
956

957
        h.reportLock.Lock()
×
958
        defer h.reportLock.Unlock()
×
959
        cpy := h.currentReport
×
960
        return &cpy
×
961
}
962

963
func (h *htlcTimeoutResolver) initReport() {
18✔
964
        // We create the initial report. This will only be reported for
18✔
965
        // resolvers not handled by the nursery.
18✔
966
        finalAmt := h.htlc.Amt.ToSatoshis()
18✔
967
        if h.htlcResolution.SignedTimeoutTx != nil {
30✔
968
                finalAmt = btcutil.Amount(
12✔
969
                        h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
12✔
970
                )
12✔
971
        }
12✔
972

973
        // If there's no timeout transaction, then we're already effectively in
974
        // level two.
975
        stage := uint32(1)
18✔
976
        if h.htlcResolution.SignedTimeoutTx == nil {
24✔
977
                stage = 2
6✔
978
        }
6✔
979

980
        h.currentReport = ContractReport{
18✔
981
                Outpoint:       h.htlcResolution.ClaimOutpoint,
18✔
982
                Type:           ReportOutputOutgoingHtlc,
18✔
983
                Amount:         finalAmt,
18✔
984
                MaturityHeight: h.htlcResolution.Expiry,
18✔
985
                LimboBalance:   finalAmt,
18✔
986
                Stage:          stage,
18✔
987
        }
18✔
988
}
989

990
// Encode writes an encoded version of the ContractResolver into the passed
991
// Writer.
992
//
993
// NOTE: Part of the ContractResolver interface.
994
func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
23✔
995
        // First, we'll write out the relevant fields of the
23✔
996
        // OutgoingHtlcResolution to the writer.
23✔
997
        if err := encodeOutgoingResolution(w, &h.htlcResolution); err != nil {
23✔
998
                return err
×
999
        }
×
1000

1001
        // With that portion written, we can now write out the fields specific
1002
        // to the resolver itself.
1003
        if err := binary.Write(w, endian, h.outputIncubating); err != nil {
23✔
1004
                return err
×
1005
        }
×
1006
        if err := binary.Write(w, endian, h.resolved); err != nil {
23✔
1007
                return err
×
1008
        }
×
1009
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
23✔
1010
                return err
×
1011
        }
×
1012

1013
        if err := binary.Write(w, endian, h.htlc.HtlcIndex); err != nil {
23✔
1014
                return err
×
1015
        }
×
1016

1017
        // We encode the sign details last for backwards compatibility.
1018
        err := encodeSignDetails(w, h.htlcResolution.SignDetails)
23✔
1019
        if err != nil {
23✔
1020
                return err
×
1021
        }
×
1022

1023
        return nil
23✔
1024
}
1025

1026
// newTimeoutResolverFromReader attempts to decode an encoded ContractResolver
1027
// from the passed Reader instance, returning an active ContractResolver
1028
// instance.
1029
func newTimeoutResolverFromReader(r io.Reader, resCfg ResolverConfig) (
1030
        *htlcTimeoutResolver, error) {
17✔
1031

17✔
1032
        h := &htlcTimeoutResolver{
17✔
1033
                contractResolverKit: *newContractResolverKit(resCfg),
17✔
1034
        }
17✔
1035

17✔
1036
        // First, we'll read out all the mandatory fields of the
17✔
1037
        // OutgoingHtlcResolution that we store.
17✔
1038
        if err := decodeOutgoingResolution(r, &h.htlcResolution); err != nil {
17✔
1039
                return nil, err
×
1040
        }
×
1041

1042
        // With those fields read, we can now read back the fields that are
1043
        // specific to the resolver itself.
1044
        if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
17✔
1045
                return nil, err
×
1046
        }
×
1047
        if err := binary.Read(r, endian, &h.resolved); err != nil {
17✔
1048
                return nil, err
×
1049
        }
×
1050
        if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
17✔
1051
                return nil, err
×
1052
        }
×
1053

1054
        if err := binary.Read(r, endian, &h.htlc.HtlcIndex); err != nil {
17✔
1055
                return nil, err
×
1056
        }
×
1057

1058
        // Sign details is a new field that was added to the htlc resolution,
1059
        // so it is serialized last for backwards compatibility. We try to read
1060
        // it, but don't error out if there are not bytes left.
1061
        signDetails, err := decodeSignDetails(r)
17✔
1062
        if err == nil {
34✔
1063
                h.htlcResolution.SignDetails = signDetails
17✔
1064
        } else if err != io.EOF && err != io.ErrUnexpectedEOF {
17✔
1065
                return nil, err
×
1066
        }
×
1067

1068
        h.initReport()
17✔
1069

17✔
1070
        return h, nil
17✔
1071
}
1072

1073
// Supplement adds additional information to the resolver that is required
1074
// before Resolve() is called.
1075
//
1076
// NOTE: Part of the htlcContractResolver interface.
1077
func (h *htlcTimeoutResolver) Supplement(htlc channeldb.HTLC) {
11✔
1078
        h.htlc = htlc
11✔
1079
}
11✔
1080

1081
// HtlcPoint returns the htlc's outpoint on the commitment tx.
1082
//
1083
// NOTE: Part of the htlcContractResolver interface.
1084
func (h *htlcTimeoutResolver) HtlcPoint() wire.OutPoint {
1✔
1085
        return h.htlcResolution.HtlcPoint()
1✔
1086
}
1✔
1087

1088
// SupplementDeadline sets the incomingHTLCExpiryHeight for this outgoing htlc
1089
// resolver.
1090
//
1091
// NOTE: Part of the htlcContractResolver interface.
1092
func (h *htlcTimeoutResolver) SupplementDeadline(d fn.Option[int32]) {
×
1093
        h.incomingHTLCExpiryHeight = d
×
1094
}
×
1095

1096
// A compile time assertion to ensure htlcTimeoutResolver meets the
1097
// ContractResolver interface.
1098
var _ htlcContractResolver = (*htlcTimeoutResolver)(nil)
1099

1100
// spendResult is used to hold the result of a spend event from either a
1101
// mempool spend or a block spend.
1102
type spendResult struct {
1103
        // spend contains the details of the spend.
1104
        spend *chainntnfs.SpendDetail
1105

1106
        // err is the error that occurred during the spend notification.
1107
        err error
1108
}
1109

1110
// waitForMempoolOrBlockSpend waits for the htlc output to be spent by a
1111
// transaction that's either be found in the mempool or in a block.
1112
func (h *htlcTimeoutResolver) waitForMempoolOrBlockSpend(op wire.OutPoint,
1113
        pkScript []byte) (*chainntnfs.SpendDetail, error) {
×
1114

×
1115
        log.Infof("%T(%v): waiting for spent of HTLC output %v to be found "+
×
1116
                "in mempool or block", h, h.htlcResolution.ClaimOutpoint, op)
×
1117

×
1118
        // Subscribe for block spent(confirmed).
×
1119
        blockSpent, err := h.Notifier.RegisterSpendNtfn(
×
1120
                &op, pkScript, h.broadcastHeight,
×
1121
        )
×
1122
        if err != nil {
×
1123
                return nil, fmt.Errorf("register spend: %w", err)
×
1124
        }
×
1125

1126
        // Subscribe for mempool spent(unconfirmed).
1127
        mempoolSpent, err := h.Mempool.SubscribeMempoolSpent(op)
×
1128
        if err != nil {
×
1129
                return nil, fmt.Errorf("register mempool spend: %w", err)
×
1130
        }
×
1131

1132
        // Create a result chan that will be used to receive the spending
1133
        // events.
1134
        result := make(chan *spendResult, 2)
×
1135

×
1136
        // Create a goroutine that will wait for either a mempool spend or a
×
1137
        // block spend.
×
1138
        //
×
1139
        // NOTE: no need to use waitgroup here as when the resolver exits, the
×
1140
        // goroutine will return on the quit channel.
×
1141
        go h.consumeSpendEvents(result, blockSpent.Spend, mempoolSpent.Spend)
×
1142

×
1143
        // Wait for the spend event to be received.
×
1144
        select {
×
1145
        case event := <-result:
×
1146
                // Cancel the mempool subscription as we don't need it anymore.
×
1147
                h.Mempool.CancelMempoolSpendEvent(mempoolSpent)
×
1148

×
1149
                return event.spend, event.err
×
1150

1151
        case <-h.quit:
×
1152
                return nil, errResolverShuttingDown
×
1153
        }
1154
}
1155

1156
// consumeSpendEvents consumes the spend events from the block and mempool
1157
// subscriptions. It exits when a spend event is received from the block, or
1158
// the resolver itself quits. When a spend event is received from the mempool,
1159
// however, it won't exit but continuing to wait for a spend event from the
1160
// block subscription.
1161
//
1162
// NOTE: there could be a case where we found the preimage in the mempool,
1163
// which will be added to our preimage beacon and settle the incoming link,
1164
// meanwhile the timeout sweep tx confirms. This outgoing HTLC is "free" money
1165
// and is not swept here.
1166
//
1167
// TODO(yy): sweep the outgoing htlc if it's confirmed.
1168
func (h *htlcTimeoutResolver) consumeSpendEvents(resultChan chan *spendResult,
1169
        blockSpent, mempoolSpent <-chan *chainntnfs.SpendDetail) {
×
1170

×
1171
        op := h.HtlcPoint()
×
1172

×
1173
        // Create a result chan to hold the results.
×
1174
        result := &spendResult{}
×
1175

×
1176
        // hasMempoolSpend is a flag that indicates whether we have found a
×
1177
        // preimage spend from the mempool. This is used to determine whether
×
1178
        // to checkpoint the resolver or not when later we found the
×
1179
        // corresponding block spend.
×
1180
        hasMempoolSpent := false
×
1181

×
1182
        // Wait for a spend event to arrive.
×
1183
        for {
×
1184
                select {
×
1185
                // If a spend event is received from the block, this outgoing
1186
                // htlc is spent either by the remote via the preimage or by us
1187
                // via the timeout. We can exit the loop and `claimCleanUp`
1188
                // will feed the preimage to the beacon if found. This treats
1189
                // the block as the final judge and the preimage spent won't
1190
                // appear in the mempool afterwards.
1191
                //
1192
                // NOTE: if a reorg happens, the preimage spend can appear in
1193
                // the mempool again. Though a rare case, we should handle it
1194
                // in a dedicated reorg system.
1195
                case spendDetail, ok := <-blockSpent:
×
1196
                        if !ok {
×
1197
                                result.err = fmt.Errorf("block spent err: %w",
×
1198
                                        errResolverShuttingDown)
×
1199
                        } else {
×
1200
                                log.Debugf("Found confirmed spend of HTLC "+
×
1201
                                        "output %s in tx=%s", op,
×
1202
                                        spendDetail.SpenderTxHash)
×
1203

×
1204
                                result.spend = spendDetail
×
1205

×
1206
                                // Once confirmed, persist the state on disk if
×
1207
                                // we haven't seen the output's spending tx in
×
1208
                                // mempool before.
×
1209
                                //
×
1210
                                // NOTE: we don't checkpoint the resolver if
×
1211
                                // it's spending tx has already been found in
×
1212
                                // mempool - the resolver will take care of the
×
1213
                                // checkpoint in its `claimCleanUp`. If we do
×
1214
                                // checkpoint here, however, we'd create a new
×
1215
                                // record in db for the same htlc resolver
×
1216
                                // which won't be cleaned up later, resulting
×
1217
                                // the channel to stay in unresolved state.
×
1218
                                //
×
1219
                                // TODO(yy): when fee bumper is implemented, we
×
1220
                                // need to further check whether this is a
×
1221
                                // preimage spend. Also need to refactor here
×
1222
                                // to save us some indentation.
×
1223
                                if !hasMempoolSpent {
×
1224
                                        result.err = h.checkPointSecondLevelTx()
×
1225
                                }
×
1226
                        }
1227

1228
                        // Send the result and exit the loop.
1229
                        resultChan <- result
×
1230

×
1231
                        return
×
1232

1233
                // If a spend event is received from the mempool, this can be
1234
                // either the 2nd stage timeout tx or a preimage spend from the
1235
                // remote. We will further check whether the spend reveals the
1236
                // preimage and add it to the preimage beacon to settle the
1237
                // incoming link.
1238
                //
1239
                // NOTE: we won't exit the loop here so we can continue to
1240
                // watch for the block spend to check point the resolution.
1241
                case spendDetail, ok := <-mempoolSpent:
×
1242
                        if !ok {
×
1243
                                result.err = fmt.Errorf("mempool spent err: %w",
×
1244
                                        errResolverShuttingDown)
×
1245

×
1246
                                // This is an internal error so we exit.
×
1247
                                resultChan <- result
×
1248

×
1249
                                return
×
1250
                        }
×
1251

1252
                        log.Debugf("Found mempool spend of HTLC output %s "+
×
1253
                                "in tx=%s", op, spendDetail.SpenderTxHash)
×
1254

×
1255
                        // Check whether the spend reveals the preimage, if not
×
1256
                        // continue the loop.
×
1257
                        hasPreimage := isPreimageSpend(
×
1258
                                h.isTaproot(), spendDetail,
×
1259
                                h.htlcResolution.SignedTimeoutTx != nil,
×
1260
                        )
×
1261
                        if !hasPreimage {
×
1262
                                log.Debugf("HTLC output %s spent doesn't "+
×
1263
                                        "reveal preimage", op)
×
1264
                                continue
×
1265
                        }
1266

1267
                        // Found the preimage spend, send the result and
1268
                        // continue the loop.
1269
                        result.spend = spendDetail
×
1270
                        resultChan <- result
×
1271

×
1272
                        // Set the hasMempoolSpent flag to true so we won't
×
1273
                        // checkpoint the resolver again in db.
×
1274
                        hasMempoolSpent = true
×
1275

×
1276
                        continue
×
1277

1278
                // If the resolver exits, we exit the goroutine.
1279
                case <-h.quit:
×
1280
                        result.err = errResolverShuttingDown
×
1281
                        resultChan <- result
×
1282

×
1283
                        return
×
1284
                }
1285
        }
1286
}
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