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

lightningnetwork / lnd / 11131178835

01 Oct 2024 06:21PM UTC coverage: 58.814%. Remained the same
11131178835

push

github

web-flow
Merge pull request #9001 from ellemouton/namespacedMC

routing: Namespaced Mission Control

211 of 257 new or added lines in 8 files covered. (82.1%)

71 existing lines in 15 files now uncovered.

130409 of 221731 relevant lines covered (58.81%)

28194.48 hits per line

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

89.24
/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 {
5✔
76

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

5✔
84
        h.initReport()
5✔
85

5✔
86
        return h
5✔
87
}
5✔
88

89
// isTaproot returns true if the htlc output is a taproot output.
90
func (h *htlcTimeoutResolver) isTaproot() bool {
61✔
91
        return txscript.IsPayToTaproot(
61✔
92
                h.htlcResolution.SweepSignDesc.Output.PkScript,
61✔
93
        )
61✔
94
}
61✔
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 {
23✔
101
        // The primary key for this resolver will be the outpoint of the HTLC
23✔
102
        // on the commitment transaction itself. If this is our commitment,
23✔
103
        // then the output can be found within the signed timeout tx,
23✔
104
        // otherwise, it's just the ClaimOutpoint.
23✔
105
        var op wire.OutPoint
23✔
106
        if h.htlcResolution.SignedTimeoutTx != nil {
35✔
107
                op = h.htlcResolution.SignedTimeoutTx.TxIn[0].PreviousOutPoint
12✔
108
        } else {
27✔
109
                op = h.htlcResolution.ClaimOutpoint
15✔
110
        }
15✔
111

112
        key := newResolverID(op)
23✔
113
        return key[:]
23✔
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) {
12✔
161

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

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

12✔
171
        // If this is the remote party's commitment, then we'll be looking for
12✔
172
        // them to spend using the second-level success transaction.
12✔
173
        var preimageBytes []byte
12✔
174
        switch {
12✔
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:
4✔
181
                //nolint:lll
4✔
182
                preimageBytes = spendingInput.Witness[taprootRemotePreimageIndex]
4✔
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:
7✔
189
                preimageBytes = spendingInput.Witness[remotePreimageIndex]
7✔
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:
4✔
195
                return nil, fmt.Errorf("breach attempt failed")
4✔
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:
9✔
207
                preimageBytes = spendingInput.Witness[localPreimageIndex]
9✔
208
        }
209

210
        preimage, err := lntypes.MakePreimage(preimageBytes)
12✔
211
        if err != nil {
12✔
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 "+
12✔
217
                "spend!", h, h.htlcResolution.ClaimOutpoint, preimage)
12✔
218

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

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

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

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

12✔
250
        return nil, h.Checkpoint(h, report)
12✔
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) {
23✔
256
        // If there's no timeout transaction, it means we are spending from a
23✔
257
        // remote commit, then the claim output is the output directly on the
23✔
258
        // commitment transaction, so we'll just use that.
23✔
259
        if h.htlcResolution.SignedTimeoutTx == nil {
33✔
260
                outPointToWatch := h.htlcResolution.ClaimOutpoint
10✔
261
                scriptToWatch := h.htlcResolution.SweepSignDesc.Output.PkScript
10✔
262

10✔
263
                return &outPointToWatch, scriptToWatch, nil
10✔
264
        }
10✔
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
17✔
274
        witness := h.htlcResolution.SignedTimeoutTx.TxIn[0].Witness
17✔
275

17✔
276
        var (
17✔
277
                scriptToWatch []byte
17✔
278
                err           error
17✔
279
        )
17✔
280
        switch {
17✔
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():
4✔
286
                // First, we'll parse the control block into something we can
4✔
287
                // use.
4✔
288
                ctrlBlockBytes := witness[len(witness)-1]
4✔
289
                ctrlBlock, err := txscript.ParseControlBlock(ctrlBlockBytes)
4✔
290
                if err != nil {
4✔
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]
4✔
297
                tapscriptRoot := ctrlBlock.RootHash(witnessScript)
4✔
298

4✔
299
                // Once we have the root, then we can derive the output key
4✔
300
                // from the internal key, then turn that into a witness
4✔
301
                // program.
4✔
302
                outputKey := txscript.ComputeTaprootOutputKey(
4✔
303
                        ctrlBlock.InternalKey, tapscriptRoot,
4✔
304
                )
4✔
305
                scriptToWatch, err = txscript.PayToTaprootScript(outputKey)
4✔
306
                if err != nil {
4✔
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:
17✔
314
                scriptToWatch, err = input.WitnessScriptHash(
17✔
315
                        witness[len(witness)-1],
17✔
316
                )
17✔
317
        }
318
        if err != nil {
17✔
319
                return nil, nil, err
×
320
        }
×
321

322
        return &outPointToWatch, scriptToWatch, nil
17✔
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 {
23✔
329

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

23✔
336
        switch {
23✔
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:
5✔
349
                return checkSizeAndIndex(
5✔
350
                        spendingWitness, remoteTaprootWitnessSuccessSize,
5✔
351
                        taprootRemotePreimageIndex,
5✔
352
                )
5✔
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:
5✔
363
                return checkSizeAndIndex(
5✔
364
                        spendingWitness, localTaprootWitnessSuccessSize,
5✔
365
                        localPreimageIndex,
5✔
366
                )
5✔
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:
9✔
380
                return checkSizeAndIndex(
9✔
381
                        spendingWitness, expectedRemoteWitnessSuccessSize,
9✔
382
                        remotePreimageIndex,
9✔
383
                )
9✔
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:
16✔
395
                fallthrough
16✔
396

397
        default:
16✔
398
                return checkSizeAndIndex(
16✔
399
                        spendingWitness, expectedLocalWitnessSuccessSize,
16✔
400
                        localPreimageIndex,
16✔
401
                )
16✔
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 {
26✔
408
        if len(witness) != size {
39✔
409
                return false
13✔
410
        }
13✔
411

412
        return len(witness[index]) == lntypes.HashSize
17✔
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) {
25✔
423

25✔
424
        // If we're already resolved, then we can exit early.
25✔
425
        if h.resolved {
31✔
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)
19✔
433
        if err != nil {
23✔
434
                return nil, err
4✔
435
        }
4✔
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(
19✔
441
                h.isTaproot(), commitSpend,
19✔
442
                h.htlcResolution.SignedTimeoutTx != nil,
19✔
443
        ) {
30✔
444

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

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

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

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

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

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

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

496
        // Calculate the budget.
497
        //
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
500
        // closes, we use all its output value plus a wallet input as the
501
        // budget. This is a temporary solution until we can optionally cancel
502
        // the incoming HTLC, more details in,
503
        // - https://github.com/lightningnetwork/lnd/issues/7969
504
        budget := calculateBudget(
6✔
505
                btcutil.Amount(inp.SignDesc().Output.Value), 2, 0,
6✔
506
        )
6✔
507

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

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

541
        return err
6✔
542
}
543

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

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

559
        h.outputIncubating = true
9✔
560

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

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

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

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

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

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

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

614
        return nil
8✔
615
}
616

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

19✔
625
        switch {
19✔
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:
6✔
631
                if err := h.sweepSecondLevelTx(immediate); err != nil {
6✔
632
                        log.Errorf("Sending timeout tx to sweeper: %v", err)
×
633

×
634
                        return nil, err
×
635
                }
×
636

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:
8✔
640
                if err := h.sweepDirectHtlcOutput(immediate); err != nil {
8✔
641
                        log.Errorf("Sending direct spend to sweeper: %v", err)
×
642

×
643
                        return nil, err
×
644
                }
×
645

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:
9✔
650
                if err := h.sendSecondLevelTxLegacy(); err != nil {
9✔
651
                        log.Errorf("Sending timeout tx to nursery: %v", err)
×
652

×
653
                        return nil, err
×
654
                }
×
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.
661
        return h.watchHtlcSpend()
19✔
662
}
663

664
// watchHtlcSpend watches for a spend of the HTLC output. For neutrino backend,
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) {
19✔
669

19✔
670
        // TODO(yy): outpointToWatch is always h.HtlcOutpoint(), can refactor
19✔
671
        // to remove the redundancy.
19✔
672
        outpointToWatch, scriptToWatch, err := h.chainDetailsToWatch()
19✔
673
        if err != nil {
19✔
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 {
35✔
680
                return h.waitForConfirmedSpend(outpointToWatch, scriptToWatch)
16✔
681
        }
16✔
682

683
        // Watch for a spend of the HTLC output in both the mempool and blocks.
684
        return h.waitForMempoolOrBlockSpend(*outpointToWatch, scriptToWatch)
3✔
685
}
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) {
16✔
691

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

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

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

709
        return spend, err
16✔
710
}
711

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

725
        return nil
19✔
726
}
727

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

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

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

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

12✔
754
        switch {
12✔
755

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

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

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

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

784
                // Deduct one block so this input is offered to the sweeper one
785
                // block earlier since the sweeper will wait for one block to
786
                // trigger the sweeping.
787
                //
788
                // TODO(yy): this is done so the outputs can be aggregated
789
                // properly. Suppose CSV locks of five 2nd-level outputs all
790
                // expire at height 840000, there is a race in block digestion
791
                // between contractcourt and sweeper:
792
                // - G1: block 840000 received in contractcourt, it now offers
793
                //   the outputs to the sweeper.
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--
6✔
801

6✔
802
                // TODO(yy): let sweeper handles the wait?
6✔
803
                err := waitForHeight(waitHeight, h.Notifier, h.quit)
6✔
804
                if err != nil {
10✔
805
                        return nil, err
4✔
806
                }
4✔
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
812
                // than one HTLC output to the same pkScript.
813
                op := &wire.OutPoint{
6✔
814
                        Hash:  *commitSpend.SpenderTxHash,
6✔
815
                        Index: commitSpend.SpenderInputIndex,
6✔
816
                }
6✔
817

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

930
// IsResolved returns true if the stored state in the resolve is fully
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 {
7✔
935
        return h.resolved
7✔
936
}
7✔
937

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

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

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

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

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

981
// Encode writes an encoded version of the ContractResolver into the passed
982
// Writer.
983
//
984
// NOTE: Part of the ContractResolver interface.
985
func (h *htlcTimeoutResolver) Encode(w io.Writer) error {
27✔
986
        // First, we'll write out the relevant fields of the
27✔
987
        // OutgoingHtlcResolution to the writer.
27✔
988
        if err := encodeOutgoingResolution(w, &h.htlcResolution); err != nil {
27✔
989
                return err
×
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 {
27✔
995
                return err
×
996
        }
×
997
        if err := binary.Write(w, endian, h.resolved); err != nil {
27✔
998
                return err
×
999
        }
×
1000
        if err := binary.Write(w, endian, h.broadcastHeight); err != nil {
27✔
1001
                return err
×
1002
        }
×
1003

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

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

1014
        return nil
27✔
1015
}
1016

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) (
1021
        *htlcTimeoutResolver, error) {
21✔
1022

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

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

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

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

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

1059
        h.initReport()
21✔
1060

21✔
1061
        return h, nil
21✔
1062
}
1063

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

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

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

1087
// A compile time assertion to ensure htlcTimeoutResolver meets the
1088
// ContractResolver interface.
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 {
1094
        // spend contains the details of the spend.
1095
        spend *chainntnfs.SpendDetail
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) {
3✔
1105

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

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

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

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

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

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

3✔
1140
                return event.spend, event.err
3✔
1141

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

1147
// consumeSpendEvents consumes the spend events from the block and mempool
1148
// subscriptions. It exits when a spend event is received from the block, or
1149
// the resolver itself quits. When a spend event is received from the mempool,
1150
// however, it won't exit but continuing to wait for a spend event from the
1151
// block subscription.
1152
//
1153
// NOTE: there could be a case where we found the preimage in the mempool,
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) {
3✔
1161

3✔
1162
        op := h.HtlcPoint()
3✔
1163

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

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

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

3✔
1195
                                result.spend = spendDetail
3✔
1196

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

1219
                        // Send the result and exit the loop.
1220
                        resultChan <- result
3✔
1221

3✔
1222
                        return
3✔
1223

1224
                // If a spend event is received from the mempool, this can be
1225
                // either the 2nd stage timeout tx or a preimage spend from the
1226
                // remote. We will further check whether the spend reveals the
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
1231
                // watch for the block spend to check point the resolution.
1232
                case spendDetail, ok := <-mempoolSpent:
3✔
1233
                        if !ok {
3✔
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

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

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

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

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

3✔
1267
                        continue
3✔
1268

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

3✔
1274
                        return
3✔
1275
                }
1276
        }
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