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

lightningnetwork / lnd / 11216766535

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

Pull #9148

github

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

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

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

67.37
/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"
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>
UNCOV
180
        case h.isTaproot() && h.htlcResolution.SignedTimeoutTx == nil:
×
UNCOV
181
                //nolint:lll
×
UNCOV
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...
UNCOV
194
        case h.isTaproot() && len(spendingInput.Witness) == 1:
×
UNCOV
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:lll
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.
UNCOV
285
        case h.isTaproot():
×
UNCOV
286
                // First, we'll parse the control block into something we can
×
UNCOV
287
                // use.
×
UNCOV
288
                ctrlBlockBytes := witness[len(witness)-1]
×
UNCOV
289
                ctrlBlock, err := txscript.ParseControlBlock(ctrlBlockBytes)
×
UNCOV
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.
UNCOV
296
                witnessScript := witness[len(witness)-2]
×
UNCOV
297
                tapscriptRoot := ctrlBlock.RootHash(witnessScript)
×
UNCOV
298

×
UNCOV
299
                // Once we have the root, then we can derive the output key
×
UNCOV
300
                // from the internal key, then turn that into a witness
×
UNCOV
301
                // program.
×
UNCOV
302
                outputKey := txscript.ComputeTaprootOutputKey(
×
UNCOV
303
                        ctrlBlock.InternalKey, tapscriptRoot,
×
UNCOV
304
                )
×
UNCOV
305
                scriptToWatch, err = txscript.PayToTaprootScript(outputKey)
×
UNCOV
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)
433
        if err != nil {
15✔
UNCOV
434
                return nil, err
×
UNCOV
435
        }
×
UNCOV
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(
441
                h.isTaproot(), commitSpend,
442
                h.htlcResolution.SignedTimeoutTx != nil,
443
        ) {
15✔
444

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

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

15✔
453
        log.Infof("%T(%v): resolving htlc with incoming fail msg, fully "+
15✔
454
                "confirmed", h, h.htlcResolution.ClaimOutpoint)
22✔
455

7✔
456
        // At this point, the second-level transaction is sufficiently
7✔
457
        // confirmed, or a transaction directly spending the output is.
7✔
458
        // Therefore, we can now send back our clean up message, failing the
7✔
459
        // HTLC on the incoming link.
7✔
460
        failureMsg := &lnwire.FailPermanentChannelFailure{}
7✔
461
        if err := h.DeliverResolutionMsg(ResolutionMsg{
7✔
462
                SourceChan: h.ShortChanID,
7✔
463
                HtlcIndex:  h.htlc.HtlcIndex,
464
                Failure:    failureMsg,
8✔
465
        }); err != nil {
8✔
466
                return nil, err
8✔
467
        }
8✔
468

8✔
469
        // Depending on whether this was a local or remote commit, we must
8✔
470
        // handle the spending transaction accordingly.
8✔
471
        return h.handleCommitSpend(commitSpend)
8✔
472
}
8✔
473

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

481
        var inp input.Input
482
        if h.isTaproot() {
8✔
483
                inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutTaprootInput(
484
                        h.htlcResolution.SignedTimeoutTx,
485
                        h.htlcResolution.SignDetails,
486
                        h.broadcastHeight,
487
                ))
2✔
488
        } else {
2✔
489
                inp = lnutils.Ptr(input.MakeHtlcSecondLevelTimeoutAnchorInput(
2✔
490
                        h.htlcResolution.SignedTimeoutTx,
2✔
491
                        h.htlcResolution.SignDetails,
2✔
492
                        h.broadcastHeight,
2✔
493
                ))
2✔
UNCOV
494
        }
×
UNCOV
495

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

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

2✔
529
        _, err := h.Sweeper.SweepInput(
2✔
530
                inp,
2✔
531
                sweep.Params{
2✔
532
                        Budget:         budget,
2✔
533
                        DeadlineHeight: h.incomingHTLCExpiryHeight,
2✔
534
                        Immediate:      immediate,
2✔
535
                },
2✔
536
        )
2✔
537
        if err != nil {
2✔
538
                return err
2✔
539
        }
2✔
540

2✔
541
        return err
2✔
542
}
2✔
543

2✔
544
// sendSecondLevelTxLegacy sends a second level timeout transaction to the utxo
2✔
545
// nursery. This transaction uses the legacy SIGHASH_ALL flag.
2✔
546
func (h *htlcTimeoutResolver) sendSecondLevelTxLegacy() error {
2✔
547
        log.Debugf("%T(%v): incubating htlc output", h,
2✔
548
                h.htlcResolution.ClaimOutpoint)
2✔
UNCOV
549

×
UNCOV
550
        err := h.IncubateOutputs(
×
551
                h.ChanPoint, fn.Some(h.htlcResolution),
552
                fn.None[lnwallet.IncomingHtlcResolution](),
2✔
553
                h.broadcastHeight, h.incomingHTLCExpiryHeight,
554
        )
555
        if err != nil {
556
                return err
557
        }
5✔
558

5✔
559
        h.outputIncubating = true
5✔
560

5✔
561
        return h.Checkpoint(h)
5✔
562
}
5✔
563

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

576
        sweepInput := input.NewCsvInputWithCltv(
577
                &h.htlcResolution.ClaimOutpoint, htlcWitnessType,
578
                &h.htlcResolution.SweepSignDesc, h.broadcastHeight,
579
                h.htlcResolution.CsvDelay, h.htlcResolution.Expiry,
4✔
580
        )
4✔
581

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

4✔
594
        log.Infof("%T(%x): offering offered remote timeout HTLC output to "+
4✔
595
                "sweeper with deadline %v and budget=%v at height=%v",
4✔
596
                h, h.htlc.RHash[:], h.incomingHTLCExpiryHeight, budget,
4✔
597
                h.broadcastHeight)
4✔
598

4✔
599
        _, err := h.Sweeper.SweepInput(
4✔
600
                sweepInput,
4✔
601
                sweep.Params{
4✔
602
                        Budget: budget,
4✔
603

4✔
604
                        // This is an outgoing HTLC, so we want to make sure
4✔
605
                        // that we sweep it before the incoming HTLC expires.
4✔
606
                        DeadlineHeight: h.incomingHTLCExpiryHeight,
4✔
607
                        Immediate:      immediate,
4✔
608
                },
4✔
609
        )
4✔
610
        if err != nil {
4✔
611
                return err
4✔
612
        }
4✔
613

4✔
614
        return nil
4✔
615
}
4✔
616

4✔
617
// spendHtlcOutput handles the initial spend of an HTLC output via the timeout
4✔
618
// clause. If this is our local commitment, the second-level timeout TX will be
4✔
619
// used to spend the output into the next stage. If this is the remote
4✔
620
// commitment, the output will be swept directly without the timeout
4✔
621
// transaction.
4✔
UNCOV
622
func (h *htlcTimeoutResolver) spendHtlcOutput(
×
UNCOV
623
        immediate bool) (*chainntnfs.SpendDetail, error) {
×
624

625
        switch {
4✔
626
        // If we have non-nil SignDetails, this means that have a 2nd level
627
        // HTLC transaction that is signed using sighash SINGLE|ANYONECANPAY
628
        // (the case for anchor type channels). In this case we can re-sign it
629
        // and attach fees at will. We let the sweeper handle this job.
630
        case h.htlcResolution.SignDetails != nil && !h.outputIncubating:
631
                if err := h.sweepSecondLevelTx(immediate); err != nil {
632
                        log.Errorf("Sending timeout tx to sweeper: %v", err)
633

634
                        return nil, err
15✔
635
                }
15✔
636

15✔
637
        // If this is a remote commitment there's no second level timeout txn,
638
        // and we can just send this directly to the sweeper.
639
        case h.htlcResolution.SignedTimeoutTx == nil && !h.outputIncubating:
640
                if err := h.sweepDirectHtlcOutput(immediate); err != nil {
641
                        log.Errorf("Sending direct spend to sweeper: %v", err)
2✔
642

2✔
643
                        return nil, err
×
644
                }
×
UNCOV
645

×
UNCOV
646
        // If we have a SignedTimeoutTx but no SignDetails, this is a local
×
647
        // commitment for a non-anchor channel, so we'll send it to the utxo
648
        // nursery.
649
        case h.htlcResolution.SignDetails == nil && !h.outputIncubating:
650
                if err := h.sendSecondLevelTxLegacy(); err != nil {
4✔
651
                        log.Errorf("Sending timeout tx to nursery: %v", err)
4✔
652

×
653
                        return nil, err
×
654
                }
×
UNCOV
655
        }
×
656

657
        // Now that we've handed off the HTLC to the nursery or sweeper, we'll
658
        // watch for a spend of the output, and make our next move off of that.
659
        // Depending on if this is our commitment, or the remote party's
660
        // commitment, we'll be watching a different outpoint and script.
5✔
661
        return h.watchHtlcSpend()
5✔
UNCOV
662
}
×
UNCOV
663

×
UNCOV
664
// watchHtlcSpend watches for a spend of the HTLC output. For neutrino backend,
×
UNCOV
665
// it will check blocks for the confirmed spend. For btcd and bitcoind, it will
×
666
// check both the mempool and the blocks.
667
func (h *htlcTimeoutResolver) watchHtlcSpend() (*chainntnfs.SpendDetail,
668
        error) {
669

670
        // TODO(yy): outpointToWatch is always h.HtlcOutpoint(), can refactor
671
        // to remove the redundancy.
672
        outpointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
15✔
673
        if err != nil {
674
                return nil, err
675
        }
676

677
        // If there's no mempool configured, which is the case for SPV node
678
        // such as neutrino, then we will watch for confirmed spend only.
679
        if h.Mempool == nil {
15✔
680
                return h.waitForConfirmedSpend(outpointToWatch, scriptToWatch)
15✔
681
        }
15✔
682

15✔
683
        // Watch for a spend of the HTLC output in both the mempool and blocks.
15✔
684
        return h.waitForMempoolOrBlockSpend(*outpointToWatch, scriptToWatch)
15✔
UNCOV
685
}
×
UNCOV
686

×
687
// waitForConfirmedSpend waits for the HTLC output to be spent and confirmed in
688
// a block, returns the spend details.
689
func (h *htlcTimeoutResolver) waitForConfirmedSpend(op *wire.OutPoint,
690
        pkScript []byte) (*chainntnfs.SpendDetail, error) {
30✔
691

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

UNCOV
695
        // We'll block here until either we exit, or the HTLC output on the
×
696
        // commitment transaction has been spent.
697
        spend, err := waitForSpend(
698
                op, pkScript, h.broadcastHeight, h.Notifier, h.quit,
699
        )
700
        if err != nil {
701
                return nil, err
15✔
702
        }
15✔
703

15✔
704
        // Once confirmed, persist the state on disk.
15✔
705
        if err := h.checkPointSecondLevelTx(); err != nil {
15✔
706
                return nil, err
15✔
707
        }
15✔
708

15✔
709
        return spend, err
15✔
710
}
15✔
711

15✔
UNCOV
712
// checkPointSecondLevelTx persists the state of a second level HTLC tx to disk
×
UNCOV
713
// if it's published by the sweeper.
×
714
func (h *htlcTimeoutResolver) checkPointSecondLevelTx() error {
715
        // If this was the second level transaction published by the sweeper,
716
        // we can checkpoint the resolver now that it's confirmed.
15✔
UNCOV
717
        if h.htlcResolution.SignDetails != nil && !h.outputIncubating {
×
UNCOV
718
                h.outputIncubating = true
×
719
                if err := h.Checkpoint(h); err != nil {
720
                        log.Errorf("unable to Checkpoint: %v", err)
15✔
721
                        return err
722
                }
723
        }
724

725
        return nil
15✔
726
}
15✔
727

15✔
728
// handleCommitSpend handles the spend of the HTLC output on the commitment
17✔
729
// transaction. If this was our local commitment, the spend will be he
2✔
730
// confirmed second-level timeout transaction, and we'll sweep that into our
2✔
UNCOV
731
// wallet. If the was a remote commitment, the resolver will resolve
×
UNCOV
732
// immetiately.
×
UNCOV
733
func (h *htlcTimeoutResolver) handleCommitSpend(
×
734
        commitSpend *chainntnfs.SpendDetail) (ContractResolver, error) {
735

736
        var (
15✔
737
                // claimOutpoint will be the outpoint of the second level
738
                // transaction, or on the remote commitment directly. It will
739
                // start out as set in the resolution, but we'll update it if
740
                // the second-level goes through the sweeper and changes its
741
                // txid.
742
                claimOutpoint = h.htlcResolution.ClaimOutpoint
743

744
                // spendTxID will be the ultimate spend of the claimOutpoint.
745
                // We set it to the commit spend for now, as this is the
8✔
746
                // ultimate spend in case this is a remote commitment. If we go
8✔
747
                // through the second-level transaction, we'll update this
8✔
748
                // accordingly.
8✔
749
                spendTxID = commitSpend.SpenderTxHash
8✔
750

8✔
751
                reports []*channeldb.ResolverReport
8✔
752
        )
8✔
753

8✔
754
        switch {
8✔
755

8✔
756
        // If we swept an HTLC directly off the remote party's commitment
8✔
757
        // transaction, then we can exit here as there's no second level sweep
8✔
758
        // to do.
8✔
759
        case h.htlcResolution.SignedTimeoutTx == nil:
8✔
760
                break
8✔
761

8✔
762
        // If the sweeper is handling the second level transaction, wait for
8✔
763
        // the CSV and possible CLTV lock to expire, before sweeping the output
8✔
764
        // on the second-level.
8✔
765
        case h.htlcResolution.SignDetails != nil:
8✔
766
                waitHeight := h.deriveWaitHeight(
767
                        h.htlcResolution.CsvDelay, commitSpend,
768
                )
769

770
                h.reportLock.Lock()
2✔
771
                h.currentReport.Stage = 2
2✔
772
                h.currentReport.MaturityHeight = waitHeight
773
                h.reportLock.Unlock()
774

775
                if h.hasCLTV() {
776
                        log.Infof("%T(%x): waiting for CSV and CLTV lock to "+
2✔
777
                                "expire at height %v", h, h.htlc.RHash[:],
2✔
778
                                waitHeight)
2✔
779
                } else {
2✔
780
                        log.Infof("%T(%x): waiting for CSV lock to expire at "+
2✔
781
                                "height %v", h, h.htlc.RHash[:], waitHeight)
2✔
782
                }
2✔
783

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

802
                // TODO(yy): let sweeper handles the wait?
803
                err := waitForHeight(waitHeight, h.Notifier, h.quit)
804
                if err != nil {
805
                        return nil, err
806
                }
807

808
                // We'll use this input index to determine the second-level
809
                // output index on the transaction, as the signatures requires
810
                // the indexes to be the same. We don't look for the
811
                // second-level output script directly, as there might be more
2✔
812
                // than one HTLC output to the same pkScript.
2✔
813
                op := &wire.OutPoint{
2✔
814
                        Hash:  *commitSpend.SpenderTxHash,
2✔
815
                        Index: commitSpend.SpenderInputIndex,
2✔
UNCOV
816
                }
×
UNCOV
817

×
818
                var csvWitnessType input.StandardWitnessType
819
                if h.isTaproot() {
820
                        //nolint:lll
821
                        csvWitnessType = input.TaprootHtlcOfferedTimeoutSecondLevel
822
                } else {
823
                        csvWitnessType = input.HtlcOfferedTimeoutSecondLevel
824
                }
2✔
825

2✔
826
                // Let the sweeper sweep the second-level output now that the
2✔
827
                // CSV/CLTV locks have expired.
2✔
828
                inp := h.makeSweepInput(
2✔
829
                        op, csvWitnessType,
2✔
830
                        input.LeaseHtlcOfferedTimeoutSecondLevel,
2✔
UNCOV
831
                        &h.htlcResolution.SweepSignDesc,
×
UNCOV
832
                        h.htlcResolution.CsvDelay,
×
833
                        uint32(commitSpend.SpendingHeight), h.htlc.RHash,
2✔
834
                )
2✔
835

2✔
836
                // Calculate the budget for this sweep.
837
                budget := calculateBudget(
838
                        btcutil.Amount(inp.SignDesc().Output.Value),
839
                        h.Budget.NoDeadlineHTLCRatio,
2✔
840
                        h.Budget.NoDeadlineHTLC,
2✔
841
                )
2✔
842

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

2✔
847
                _, err = h.Sweeper.SweepInput(
2✔
848
                        inp,
2✔
849
                        sweep.Params{
2✔
850
                                Budget: budget,
2✔
851

2✔
852
                                // For second level success tx, there's no rush
2✔
853
                                // to get it confirmed, so we use a nil
2✔
854
                                // deadline.
2✔
855
                                DeadlineHeight: fn.None[int32](),
2✔
856
                        },
2✔
857
                )
2✔
858
                if err != nil {
2✔
859
                        return nil, err
2✔
860
                }
2✔
861

2✔
862
                // Update the claim outpoint to point to the second-level
2✔
863
                // transaction created by the sweeper.
2✔
864
                claimOutpoint = *op
2✔
865
                fallthrough
2✔
866

2✔
867
        // Finally, if this was an output on our commitment transaction, we'll
2✔
868
        // wait for the second-level HTLC output to be spent, and for that
2✔
869
        // transaction itself to confirm.
2✔
UNCOV
870
        case h.htlcResolution.SignedTimeoutTx != nil:
×
UNCOV
871
                log.Infof("%T(%v): waiting for nursery/sweeper to spend CSV "+
×
872
                        "delayed output", h, claimOutpoint)
873

874
                sweepTx, err := waitForSpend(
875
                        &claimOutpoint,
2✔
876
                        h.htlcResolution.SweepSignDesc.Output.PkScript,
2✔
877
                        h.broadcastHeight, h.Notifier, h.quit,
878
                )
879
                if err != nil {
880
                        return nil, err
881
                }
6✔
882

6✔
883
                // Update the spend txid to the hash of the sweep transaction.
6✔
884
                spendTxID = sweepTx.SpenderTxHash
6✔
885

6✔
886
                // Once our sweep of the timeout tx has confirmed, we add a
6✔
887
                // resolution for our timeoutTx tx first stage transaction.
6✔
888
                timeoutTx := commitSpend.SpendingTx
6✔
889
                index := commitSpend.SpenderInputIndex
6✔
890
                spendHash := commitSpend.SpenderTxHash
6✔
UNCOV
891

×
UNCOV
892
                reports = append(reports, &channeldb.ResolverReport{
×
893
                        OutPoint:        timeoutTx.TxIn[index].PreviousOutPoint,
894
                        Amount:          h.htlc.Amt.ToSatoshis(),
895
                        ResolverType:    channeldb.ResolverTypeOutgoingHtlc,
6✔
896
                        ResolverOutcome: channeldb.ResolverOutcomeFirstStage,
6✔
897
                        SpendTxID:       spendHash,
6✔
898
                })
6✔
899
        }
6✔
900

6✔
901
        // With the clean up message sent, we'll now mark the contract
6✔
902
        // resolved, update the recovered balance, record the timeout and the
6✔
903
        // sweep txid on disk, and wait.
6✔
904
        h.resolved = true
6✔
905
        h.reportLock.Lock()
6✔
906
        h.currentReport.RecoveredBalance = h.currentReport.LimboBalance
6✔
907
        h.currentReport.LimboBalance = 0
6✔
908
        h.reportLock.Unlock()
6✔
909

6✔
910
        amt := btcutil.Amount(h.htlcResolution.SweepSignDesc.Output.Value)
911
        reports = append(reports, &channeldb.ResolverReport{
912
                OutPoint:        claimOutpoint,
913
                Amount:          amt,
914
                ResolverType:    channeldb.ResolverTypeOutgoingHtlc,
915
                ResolverOutcome: channeldb.ResolverOutcomeTimeout,
8✔
916
                SpendTxID:       spendTxID,
8✔
917
        })
8✔
918

8✔
919
        return nil, h.Checkpoint(h, reports...)
8✔
920
}
8✔
921

8✔
922
// Stop signals the resolver to cancel any current resolution processes, and
8✔
923
// suspend.
8✔
924
//
8✔
925
// NOTE: Part of the ContractResolver interface.
8✔
926
func (h *htlcTimeoutResolver) Stop() {
8✔
927
        close(h.quit)
8✔
928
}
8✔
929

8✔
930
// IsResolved returns true if the stored state in the resolve is fully
8✔
931
// resolved. In this case the target output can be forgotten.
932
//
933
// NOTE: Part of the ContractResolver interface.
934
func (h *htlcTimeoutResolver) IsResolved() bool {
935
        return h.resolved
936
}
937

1✔
938
// report returns a report on the resolution state of the contract.
1✔
939
func (h *htlcTimeoutResolver) report() *ContractReport {
1✔
940
        // If we have a SignedTimeoutTx but no SignDetails, this is a local
941
        // commitment for a non-anchor channel, which was handled by the utxo
942
        // nursery.
943
        if h.htlcResolution.SignDetails == nil && h.
944
                htlcResolution.SignedTimeoutTx != nil {
945
                return nil
3✔
946
        }
3✔
947

3✔
948
        h.reportLock.Lock()
949
        defer h.reportLock.Unlock()
UNCOV
950
        cpy := h.currentReport
×
UNCOV
951
        return &cpy
×
UNCOV
952
}
×
UNCOV
953

×
UNCOV
954
func (h *htlcTimeoutResolver) initReport() {
×
UNCOV
955
        // We create the initial report. This will only be reported for
×
UNCOV
956
        // resolvers not handled by the nursery.
×
UNCOV
957
        finalAmt := h.htlc.Amt.ToSatoshis()
×
958
        if h.htlcResolution.SignedTimeoutTx != nil {
UNCOV
959
                finalAmt = btcutil.Amount(
×
UNCOV
960
                        h.htlcResolution.SignedTimeoutTx.TxOut[0].Value,
×
UNCOV
961
                )
×
UNCOV
962
        }
×
963

964
        // If there's no timeout transaction, then we're already effectively in
965
        // level two.
18✔
966
        stage := uint32(1)
18✔
967
        if h.htlcResolution.SignedTimeoutTx == nil {
18✔
968
                stage = 2
18✔
969
        }
30✔
970

12✔
971
        h.currentReport = ContractReport{
12✔
972
                Outpoint:       h.htlcResolution.ClaimOutpoint,
12✔
973
                Type:           ReportOutputOutgoingHtlc,
12✔
974
                Amount:         finalAmt,
975
                MaturityHeight: h.htlcResolution.Expiry,
976
                LimboBalance:   finalAmt,
977
                Stage:          stage,
18✔
978
        }
24✔
979
}
6✔
980

6✔
981
// Encode writes an encoded version of the ContractResolver into the passed
982
// Writer.
18✔
983
//
18✔
984
// NOTE: Part of the ContractResolver interface.
18✔
985
func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
18✔
986
        // First, we'll write out the relevant fields of the
18✔
987
        // OutgoingHtlcResolution to the writer.
18✔
988
        if err := encodeOutgoingResolution(w, &h.htlcResolution); err != nil {
18✔
989
                return err
18✔
990
        }
991

992
        // With that portion written, we can now write out the fields specific
993
        // to the resolver itself.
994
        if err := binary.Write(w, endian, h.outputIncubating); err != nil {
995
                return err
996
        }
23✔
997
        if err := binary.Write(w, endian, h.resolved); err != nil {
23✔
998
                return err
23✔
999
        }
23✔
UNCOV
1000
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
×
1001
                return err
×
1002
        }
1003

1004
        if err := binary.Write(w, endian, h.htlc.HtlcIndex); err != nil {
1005
                return err
23✔
1006
        }
×
UNCOV
1007

×
1008
        // We encode the sign details last for backwards compatibility.
23✔
UNCOV
1009
        err := encodeSignDetails(w, h.htlcResolution.SignDetails)
×
UNCOV
1010
        if err != nil {
×
1011
                return err
23✔
1012
        }
×
UNCOV
1013

×
1014
        return nil
1015
}
23✔
UNCOV
1016

×
UNCOV
1017
// newTimeoutResolverFromReader attempts to decode an encoded ContractResolver
×
1018
// from the passed Reader instance, returning an active ContractResolver
1019
// instance.
1020
func newTimeoutResolverFromReader(r io.Reader, resCfg ResolverConfig) (
23✔
1021
        *htlcTimeoutResolver, error) {
23✔
UNCOV
1022

×
UNCOV
1023
        h := &htlcTimeoutResolver{
×
1024
                contractResolverKit: *newContractResolverKit(resCfg),
1025
        }
23✔
1026

1027
        // First, we'll read out all the mandatory fields of the
1028
        // OutgoingHtlcResolution that we store.
1029
        if err := decodeOutgoingResolution(r, &h.htlcResolution); err != nil {
1030
                return nil, err
1031
        }
1032

17✔
1033
        // With those fields read, we can now read back the fields that are
17✔
1034
        // specific to the resolver itself.
17✔
1035
        if err := binary.Read(r, endian, &h.outputIncubating); err != nil {
17✔
1036
                return nil, err
17✔
1037
        }
17✔
1038
        if err := binary.Read(r, endian, &h.resolved); err != nil {
17✔
1039
                return nil, err
17✔
1040
        }
17✔
UNCOV
1041
        if err := binary.Read(r, endian, &h.broadcastHeight); err != nil {
×
1042
                return nil, err
×
1043
        }
1044

1045
        if err := binary.Read(r, endian, &h.htlc.HtlcIndex); err != nil {
1046
                return nil, err
17✔
1047
        }
×
UNCOV
1048

×
1049
        // Sign details is a new field that was added to the htlc resolution,
17✔
UNCOV
1050
        // so it is serialized last for backwards compatibility. We try to read
×
UNCOV
1051
        // it, but don't error out if there are not bytes left.
×
1052
        signDetails, err := decodeSignDetails(r)
17✔
UNCOV
1053
        if err == nil {
×
UNCOV
1054
                h.htlcResolution.SignDetails = signDetails
×
1055
        } else if err != io.EOF && err != io.ErrUnexpectedEOF {
1056
                return nil, err
17✔
1057
        }
×
UNCOV
1058

×
1059
        h.initReport()
1060

1061
        return h, nil
1062
}
1063

17✔
1064
// Supplement adds additional information to the resolver that is required
34✔
1065
// before Resolve() is called.
17✔
1066
//
17✔
UNCOV
1067
// NOTE: Part of the htlcContractResolver interface.
×
UNCOV
1068
func (h *htlcTimeoutResolver) Supplement(htlc channeldb.HTLC) {
×
1069
        h.htlc = htlc
1070
}
17✔
1071

17✔
1072
// HtlcPoint returns the htlc's outpoint on the commitment tx.
17✔
1073
//
1074
// NOTE: Part of the htlcContractResolver interface.
1075
func (h *htlcTimeoutResolver) HtlcPoint() wire.OutPoint {
1076
        return h.htlcResolution.HtlcPoint()
1077
}
1078

1079
// SupplementDeadline sets the incomingHTLCExpiryHeight for this outgoing htlc
11✔
1080
// resolver.
11✔
1081
//
11✔
1082
// NOTE: Part of the htlcContractResolver interface.
1083
func (h *htlcTimeoutResolver) SupplementDeadline(d fn.Option[int32]) {
1084
        h.incomingHTLCExpiryHeight = d
1085
}
1086

1✔
1087
// A compile time assertion to ensure htlcTimeoutResolver meets the
1✔
1088
// ContractResolver interface.
1✔
1089
var _ htlcContractResolver = (*htlcTimeoutResolver)(nil)
1090

1091
// spendResult is used to hold the result of a spend event from either a
1092
// mempool spend or a block spend.
1093
type spendResult struct {
UNCOV
1094
        // spend contains the details of the spend.
×
UNCOV
1095
        spend *chainntnfs.SpendDetail
×
UNCOV
1096

×
1097
        // err is the error that occurred during the spend notification.
1098
        err error
1099
}
1100

1101
// waitForMempoolOrBlockSpend waits for the htlc output to be spent by a
1102
// transaction that's either be found in the mempool or in a block.
1103
func (h *htlcTimeoutResolver) waitForMempoolOrBlockSpend(op wire.OutPoint,
1104
        pkScript []byte) (*chainntnfs.SpendDetail, error) {
1105

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

1109
        // Subscribe for block spent(confirmed).
1110
        blockSpent, err := h.Notifier.RegisterSpendNtfn(
1111
                &op, pkScript, h.broadcastHeight,
1112
        )
1113
        if err != nil {
1114
                return nil, fmt.Errorf("register spend: %w", err)
1115
        }
×
UNCOV
1116

×
UNCOV
1117
        // Subscribe for mempool spent(unconfirmed).
×
UNCOV
1118
        mempoolSpent, err := h.Mempool.SubscribeMempoolSpent(op)
×
UNCOV
1119
        if err != nil {
×
1120
                return nil, fmt.Errorf("register mempool spend: %w", err)
×
1121
        }
×
UNCOV
1122

×
UNCOV
1123
        // Create a result chan that will be used to receive the spending
×
UNCOV
1124
        // events.
×
UNCOV
1125
        result := make(chan *spendResult, 2)
×
UNCOV
1126

×
1127
        // Create a goroutine that will wait for either a mempool spend or a
1128
        // block spend.
UNCOV
1129
        //
×
UNCOV
1130
        // NOTE: no need to use waitgroup here as when the resolver exits, the
×
UNCOV
1131
        // goroutine will return on the quit channel.
×
UNCOV
1132
        go h.consumeSpendEvents(result, blockSpent.Spend, mempoolSpent.Spend)
×
1133

1134
        // Wait for the spend event to be received.
1135
        select {
UNCOV
1136
        case event := <-result:
×
UNCOV
1137
                // Cancel the mempool subscription as we don't need it anymore.
×
UNCOV
1138
                h.Mempool.CancelMempoolSpendEvent(mempoolSpent)
×
UNCOV
1139

×
UNCOV
1140
                return event.spend, event.err
×
UNCOV
1141

×
UNCOV
1142
        case <-h.quit:
×
UNCOV
1143
                return nil, errResolverShuttingDown
×
UNCOV
1144
        }
×
UNCOV
1145
}
×
UNCOV
1146

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

1162
        op := h.HtlcPoint()
1163

1164
        // Create a result chan to hold the results.
1165
        result := &spendResult{}
1166

1167
        // hasMempoolSpend is a flag that indicates whether we have found a
1168
        // preimage spend from the mempool. This is used to determine whether
1169
        // to checkpoint the resolver or not when later we found the
1170
        // corresponding block spend.
UNCOV
1171
        hasMempoolSpent := false
×
UNCOV
1172

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

1195
                                result.spend = spendDetail
1196

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

×
UNCOV
1219
                        // Send the result and exit the loop.
×
UNCOV
1220
                        resultChan <- result
×
UNCOV
1221

×
UNCOV
1222
                        return
×
UNCOV
1223

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

1237
                                // This is an internal error so we exit.
1238
                                resultChan <- result
1239

1240
                                return
1241
                        }
1242

UNCOV
1243
                        log.Debugf("Found mempool spend of HTLC output %s "+
×
UNCOV
1244
                                "in tx=%s", op, spendDetail.SpenderTxHash)
×
UNCOV
1245

×
UNCOV
1246
                        // Check whether the spend reveals the preimage, if not
×
UNCOV
1247
                        // continue the loop.
×
UNCOV
1248
                        hasPreimage := isPreimageSpend(
×
UNCOV
1249
                                h.isTaproot(), spendDetail,
×
UNCOV
1250
                                h.htlcResolution.SignedTimeoutTx != nil,
×
UNCOV
1251
                        )
×
UNCOV
1252
                        if !hasPreimage {
×
1253
                                log.Debugf("HTLC output %s spent doesn't "+
UNCOV
1254
                                        "reveal preimage", op)
×
UNCOV
1255
                                continue
×
UNCOV
1256
                        }
×
UNCOV
1257

×
UNCOV
1258
                        // Found the preimage spend, send the result and
×
UNCOV
1259
                        // continue the loop.
×
UNCOV
1260
                        result.spend = spendDetail
×
UNCOV
1261
                        resultChan <- result
×
UNCOV
1262

×
UNCOV
1263
                        // Set the hasMempoolSpent flag to true so we won't
×
UNCOV
1264
                        // checkpoint the resolver again in db.
×
UNCOV
1265
                        hasMempoolSpent = true
×
UNCOV
1266

×
1267
                        continue
1268

1269
                // If the resolver exits, we exit the goroutine.
1270
                case <-h.quit:
UNCOV
1271
                        result.err = errResolverShuttingDown
×
UNCOV
1272
                        resultChan <- result
×
UNCOV
1273

×
UNCOV
1274
                        return
×
UNCOV
1275
                }
×
UNCOV
1276
        }
×
UNCOV
1277
}
×
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