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

lightningnetwork / lnd / 12320026784

13 Dec 2024 05:14PM UTC coverage: 57.562% (-1.1%) from 58.636%
12320026784

Pull #9315

github

web-flow
Merge pull request #9277 from yyforyongyu/yy-blockbeat-end

Beat [4/4]: implement `Consumer` in `chainWatcher`
Pull Request #9315: Implement `blockbeat`

1754 of 2518 new or added lines in 30 files covered. (69.66%)

19108 existing lines in 247 files now uncovered.

102548 of 178151 relevant lines covered (57.56%)

24794.02 hits per line

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

71.91
/contractcourt/htlc_incoming_contest_resolver.go
1
package contractcourt
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "errors"
7
        "fmt"
8
        "io"
9

10
        "github.com/btcsuite/btcd/btcutil"
11
        "github.com/btcsuite/btcd/txscript"
12
        "github.com/lightningnetwork/lnd/channeldb"
13
        "github.com/lightningnetwork/lnd/fn/v2"
14
        "github.com/lightningnetwork/lnd/graph/db/models"
15
        "github.com/lightningnetwork/lnd/htlcswitch/hop"
16
        "github.com/lightningnetwork/lnd/invoices"
17
        "github.com/lightningnetwork/lnd/lntypes"
18
        "github.com/lightningnetwork/lnd/lnwallet"
19
        "github.com/lightningnetwork/lnd/lnwire"
20
        "github.com/lightningnetwork/lnd/queue"
21
)
22

23
// htlcIncomingContestResolver is a ContractResolver that's able to resolve an
24
// incoming HTLC that is still contested. An HTLC is still contested, if at the
25
// time of commitment broadcast, we don't know of the preimage for it yet, and
26
// it hasn't expired. In this case, we can resolve the HTLC if we learn of the
27
// preimage, otherwise the remote party will sweep it after it expires.
28
//
29
// TODO(roasbeef): just embed the other resolver?
30
type htlcIncomingContestResolver struct {
31
        // htlcExpiry is the absolute expiry of this incoming HTLC. We use this
32
        // value to determine if we can exit early as if the HTLC times out,
33
        // before we learn of the preimage then we can't claim it on chain
34
        // successfully.
35
        htlcExpiry uint32
36

37
        // htlcSuccessResolver is the inner resolver that may be utilized if we
38
        // learn of the preimage.
39
        *htlcSuccessResolver
40
}
41

42
// newIncomingContestResolver instantiates a new incoming htlc contest resolver.
43
func newIncomingContestResolver(
44
        res lnwallet.IncomingHtlcResolution, broadcastHeight uint32,
UNCOV
45
        htlc channeldb.HTLC, resCfg ResolverConfig) *htlcIncomingContestResolver {
×
UNCOV
46

×
UNCOV
47
        success := newSuccessResolver(
×
UNCOV
48
                res, broadcastHeight, htlc, resCfg,
×
UNCOV
49
        )
×
UNCOV
50

×
UNCOV
51
        return &htlcIncomingContestResolver{
×
UNCOV
52
                htlcExpiry:          htlc.RefundTimeout,
×
UNCOV
53
                htlcSuccessResolver: success,
×
UNCOV
54
        }
×
UNCOV
55
}
×
56

57
func (h *htlcIncomingContestResolver) processFinalHtlcFail() error {
5✔
58
        // Mark the htlc as final failed.
5✔
59
        err := h.ChainArbitratorConfig.PutFinalHtlcOutcome(
5✔
60
                h.ChannelArbitratorConfig.ShortChanID, h.htlc.HtlcIndex, false,
5✔
61
        )
5✔
62
        if err != nil {
5✔
63
                return err
×
64
        }
×
65

66
        // Send notification.
67
        h.ChainArbitratorConfig.HtlcNotifier.NotifyFinalHtlcEvent(
5✔
68
                models.CircuitKey{
5✔
69
                        ChanID: h.ShortChanID,
5✔
70
                        HtlcID: h.htlc.HtlcIndex,
5✔
71
                },
5✔
72
                channeldb.FinalHtlcInfo{
5✔
73
                        Settled:  false,
5✔
74
                        Offchain: false,
5✔
75
                },
5✔
76
        )
5✔
77

5✔
78
        return nil
5✔
79
}
80

81
// Launch will call the inner resolver's launch method if the preimage can be
82
// found, otherwise it's a no-op.
83
func (h *htlcIncomingContestResolver) Launch() error {
9✔
84
        // NOTE: we don't mark this resolver as launched as the inner resolver
9✔
85
        // will set it when it's launched.
9✔
86
        if h.isLaunched() {
9✔
NEW
87
                h.log.Tracef("already launched")
×
NEW
88
                return nil
×
NEW
89
        }
×
90

91
        h.log.Debugf("launching contest resolver...")
9✔
92

9✔
93
        // Query the preimage and apply it if we already know it.
9✔
94
        applied, err := h.findAndapplyPreimage()
9✔
95
        if err != nil {
9✔
NEW
96
                return err
×
NEW
97
        }
×
98

99
        // No preimage found, leave it to be handled by the resolver.
100
        if !applied {
15✔
101
                return nil
6✔
102
        }
6✔
103

104
        h.log.Debugf("found preimage for htlc=%x,  transforming into success "+
3✔
105
                "resolver and launching it", h.htlc.RHash)
3✔
106

3✔
107
        // Once we've applied the preimage, we'll launch the inner resolver to
3✔
108
        // attempt to claim the HTLC.
3✔
109
        return h.htlcSuccessResolver.Launch()
3✔
110
}
111

112
// Resolve attempts to resolve this contract. As we don't yet know of the
113
// preimage for the contract, we'll wait for one of two things to happen:
114
//
115
//  1. We learn of the preimage! In this case, we can sweep the HTLC incoming
116
//     and ensure that if this was a multi-hop HTLC we are made whole. In this
117
//     case, an additional ContractResolver will be returned to finish the
118
//     job.
119
//
120
//  2. The HTLC expires. If this happens, then the contract is fully resolved
121
//     as we have no remaining actions left at our disposal.
122
//
123
// NOTE: Part of the ContractResolver interface.
124
func (h *htlcIncomingContestResolver) Resolve() (ContractResolver, error) {
9✔
125
        // If we're already full resolved, then we don't have anything further
9✔
126
        // to do.
9✔
127
        if h.IsResolved() {
9✔
NEW
128
                h.log.Errorf("already resolved")
×
129
                return nil, nil
×
130
        }
×
131

132
        // First try to parse the payload. If that fails, we can stop resolution
133
        // now.
134
        payload, nextHopOnionBlob, err := h.decodePayload()
9✔
135
        if err != nil {
9✔
NEW
136
                h.log.Debugf("cannot decode payload of htlc %v", h.HtlcPoint())
×
137

×
138
                // If we've locked in an htlc with an invalid payload on our
×
139
                // commitment tx, we don't need to resolve it. The other party
×
140
                // will time it out and get their funds back. This situation
×
141
                // can present itself when we crash before processRemoteAdds in
×
142
                // the link has ran.
×
NEW
143
                h.markResolved()
×
144

×
145
                if err := h.processFinalHtlcFail(); err != nil {
×
146
                        return nil, err
×
147
                }
×
148

149
                // We write a report to disk that indicates we could not decode
150
                // the htlc.
151
                resReport := h.report().resolverReport(
×
152
                        nil, channeldb.ResolverTypeIncomingHtlc,
×
153
                        channeldb.ResolverOutcomeAbandoned,
×
154
                )
×
155
                return nil, h.PutResolverReport(nil, resReport)
×
156
        }
157

158
        // Register for block epochs. After registration, the current height
159
        // will be sent on the channel immediately.
160
        blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
9✔
161
        if err != nil {
9✔
162
                return nil, err
×
163
        }
×
164
        defer blockEpochs.Cancel()
9✔
165

9✔
166
        var currentHeight int32
9✔
167
        select {
9✔
168
        case newBlock, ok := <-blockEpochs.Epochs:
9✔
169
                if !ok {
9✔
170
                        return nil, errResolverShuttingDown
×
171
                }
×
172
                currentHeight = newBlock.Height
9✔
173
        case <-h.quit:
×
174
                return nil, errResolverShuttingDown
×
175
        }
176

177
        log.Debugf("%T(%v): Resolving incoming HTLC(expiry=%v, height=%v)", h,
9✔
178
                h.htlcResolution.ClaimOutpoint, h.htlcExpiry, currentHeight)
9✔
179

9✔
180
        // We'll first check if this HTLC has been timed out, if so, we can
9✔
181
        // return now and mark ourselves as resolved. If we're past the point of
9✔
182
        // expiry of the HTLC, then at this point the sender can sweep it, so
9✔
183
        // we'll end our lifetime. Here we deliberately forego the chance that
9✔
184
        // the sender doesn't sweep and we already have or will learn the
9✔
185
        // preimage. Otherwise the resolver could potentially stay active
9✔
186
        // indefinitely and the channel will never close properly.
9✔
187
        if uint32(currentHeight) >= h.htlcExpiry {
10✔
188
                // TODO(roasbeef): should also somehow check if outgoing is
1✔
189
                // resolved or not
1✔
190
                //  * may need to hook into the circuit map
1✔
191
                //  * can't timeout before the outgoing has been
1✔
192

1✔
193
                log.Infof("%T(%v): HTLC has timed out (expiry=%v, height=%v), "+
1✔
194
                        "abandoning", h, h.htlcResolution.ClaimOutpoint,
1✔
195
                        h.htlcExpiry, currentHeight)
1✔
196
                h.markResolved()
1✔
197

1✔
198
                if err := h.processFinalHtlcFail(); err != nil {
1✔
199
                        return nil, err
×
200
                }
×
201

202
                // Finally, get our report and checkpoint our resolver with a
203
                // timeout outcome report.
204
                report := h.report().resolverReport(
1✔
205
                        nil, channeldb.ResolverTypeIncomingHtlc,
1✔
206
                        channeldb.ResolverOutcomeTimeout,
1✔
207
                )
1✔
208
                return nil, h.Checkpoint(h, report)
1✔
209
        }
210

211
        // Define a closure to process htlc resolutions either directly or
212
        // triggered by future notifications.
213
        processHtlcResolution := func(e invoices.HtlcResolution) (
8✔
214
                ContractResolver, error) {
12✔
215

4✔
216
                // Take action based on the type of resolution we have
4✔
217
                // received.
4✔
218
                switch resolution := e.(type) {
4✔
219
                // If the htlc resolution was a settle, apply the
220
                // preimage and return a success resolver.
221
                case *invoices.HtlcSettleResolution:
2✔
222
                        err := h.applyPreimage(resolution.Preimage)
2✔
223
                        if err != nil {
2✔
224
                                return nil, err
×
225
                        }
×
226

227
                        return h.htlcSuccessResolver, nil
2✔
228

229
                // If the htlc was failed, mark the htlc as
230
                // resolved.
231
                case *invoices.HtlcFailResolution:
2✔
232
                        log.Infof("%T(%v): Exit hop HTLC canceled "+
2✔
233
                                "(expiry=%v, height=%v), abandoning", h,
2✔
234
                                h.htlcResolution.ClaimOutpoint,
2✔
235
                                h.htlcExpiry, currentHeight)
2✔
236

2✔
237
                        h.markResolved()
2✔
238

2✔
239
                        if err := h.processFinalHtlcFail(); err != nil {
2✔
240
                                return nil, err
×
241
                        }
×
242

243
                        // Checkpoint our resolver with an abandoned outcome
244
                        // because we take no further action on this htlc.
245
                        report := h.report().resolverReport(
2✔
246
                                nil, channeldb.ResolverTypeIncomingHtlc,
2✔
247
                                channeldb.ResolverOutcomeAbandoned,
2✔
248
                        )
2✔
249
                        return nil, h.Checkpoint(h, report)
2✔
250

251
                // Error if the resolution type is unknown, we are only
252
                // expecting settles and fails.
253
                default:
×
254
                        return nil, fmt.Errorf("unknown resolution"+
×
255
                                " type: %v", e)
×
256
                }
257
        }
258

259
        var (
8✔
260
                hodlChan       <-chan interface{}
8✔
261
                witnessUpdates <-chan lntypes.Preimage
8✔
262
        )
8✔
263
        if payload.FwdInfo.NextHop == hop.Exit {
13✔
264
                // Create a buffered hodl chan to prevent deadlock.
5✔
265
                hodlQueue := queue.NewConcurrentQueue(10)
5✔
266
                hodlQueue.Start()
5✔
267

5✔
268
                hodlChan = hodlQueue.ChanOut()
5✔
269

5✔
270
                // Notify registry that we are potentially resolving as an exit
5✔
271
                // hop on-chain. If this HTLC indeed pays to an existing
5✔
272
                // invoice, the invoice registry will tell us what to do with
5✔
273
                // the HTLC. This is identical to HTLC resolution in the link.
5✔
274
                circuitKey := models.CircuitKey{
5✔
275
                        ChanID: h.ShortChanID,
5✔
276
                        HtlcID: h.htlc.HtlcIndex,
5✔
277
                }
5✔
278

5✔
279
                resolution, err := h.Registry.NotifyExitHopHtlc(
5✔
280
                        h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
5✔
281
                        circuitKey, hodlQueue.ChanIn(), nil, payload,
5✔
282
                )
5✔
283
                if err != nil {
5✔
284
                        return nil, err
×
285
                }
×
286

287
                h.log.Debugf("received resolution from registry: %v",
5✔
288
                        resolution)
5✔
289

5✔
290
                defer func() {
10✔
291
                        h.Registry.HodlUnsubscribeAll(hodlQueue.ChanIn())
5✔
292

5✔
293
                        hodlQueue.Stop()
5✔
294
                }()
5✔
295

296
                // Take action based on the resolution we received. If the htlc
297
                // was settled, or a htlc for a known invoice failed we can
298
                // resolve it directly. If the resolution is nil, the htlc was
299
                // neither accepted nor failed, so we cannot take action yet.
300
                switch res := resolution.(type) {
5✔
301
                case *invoices.HtlcFailResolution:
1✔
302
                        // In the case where the htlc failed, but the invoice
1✔
303
                        // was known to the registry, we can directly resolve
1✔
304
                        // the htlc.
1✔
305
                        if res.Outcome != invoices.ResultInvoiceNotFound {
2✔
306
                                return processHtlcResolution(resolution)
1✔
307
                        }
1✔
308

309
                // If we settled the htlc, we can resolve it.
310
                case *invoices.HtlcSettleResolution:
1✔
311
                        return processHtlcResolution(resolution)
1✔
312

313
                // If the resolution is nil, the htlc was neither settled nor
314
                // failed so we cannot take action at present.
315
                case nil:
3✔
316

317
                default:
×
318
                        return nil, fmt.Errorf("unknown htlc resolution type: %T",
×
319
                                resolution)
×
320
                }
321
        } else {
3✔
322
                // If the HTLC hasn't expired yet, then we may still be able to
3✔
323
                // claim it if we learn of the pre-image, so we'll subscribe to
3✔
324
                // the preimage database to see if it turns up, or the HTLC
3✔
325
                // times out.
3✔
326
                //
3✔
327
                // NOTE: This is done BEFORE opportunistically querying the db,
3✔
328
                // to ensure the preimage can't be delivered between querying
3✔
329
                // and registering for the preimage subscription.
3✔
330
                preimageSubscription, err := h.PreimageDB.SubscribeUpdates(
3✔
331
                        h.htlcSuccessResolver.ShortChanID, &h.htlc,
3✔
332
                        payload, nextHopOnionBlob,
3✔
333
                )
3✔
334
                if err != nil {
3✔
335
                        return nil, err
×
336
                }
×
337
                defer preimageSubscription.CancelSubscription()
3✔
338

3✔
339
                // With the epochs and preimage subscriptions initialized, we'll
3✔
340
                // query to see if we already know the preimage.
3✔
341
                preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
3✔
342
                if ok {
4✔
343
                        // If we do, then this means we can claim the HTLC!
1✔
344
                        // However, we don't know how to ourselves, so we'll
1✔
345
                        // return our inner resolver which has the knowledge to
1✔
346
                        // do so.
1✔
347
                        h.log.Debugf("Found preimage for htlc=%x", h.htlc.RHash)
1✔
348

1✔
349
                        if err := h.applyPreimage(preimage); err != nil {
1✔
350
                                return nil, err
×
351
                        }
×
352

353
                        return h.htlcSuccessResolver, nil
1✔
354
                }
355

356
                witnessUpdates = preimageSubscription.WitnessUpdates
2✔
357
        }
358

359
        for {
11✔
360
                select {
6✔
361
                case preimage := <-witnessUpdates:
1✔
362
                        // We received a new preimage, but we need to ignore
1✔
363
                        // all except the preimage we are waiting for.
1✔
364
                        if !preimage.Matches(h.htlc.RHash) {
1✔
UNCOV
365
                                continue
×
366
                        }
367

368
                        h.log.Debugf("Received preimage for htlc=%x",
1✔
369
                                h.htlc.RHash)
1✔
370

1✔
371
                        if err := h.applyPreimage(preimage); err != nil {
1✔
372
                                return nil, err
×
373
                        }
×
374

375
                        // We've learned of the preimage and this information
376
                        // has been added to our inner resolver. We return it so
377
                        // it can continue contract resolution.
378
                        return h.htlcSuccessResolver, nil
1✔
379

380
                case hodlItem := <-hodlChan:
2✔
381
                        htlcResolution := hodlItem.(invoices.HtlcResolution)
2✔
382
                        return processHtlcResolution(htlcResolution)
2✔
383

384
                case newBlock, ok := <-blockEpochs.Epochs:
3✔
385
                        if !ok {
3✔
386
                                return nil, errResolverShuttingDown
×
387
                        }
×
388

389
                        // If this new height expires the HTLC, then this means
390
                        // we never found out the preimage, so we can mark
391
                        // resolved and exit.
392
                        newHeight := uint32(newBlock.Height)
3✔
393
                        if newHeight >= h.htlcExpiry {
5✔
394
                                log.Infof("%T(%v): HTLC has timed out "+
2✔
395
                                        "(expiry=%v, height=%v), abandoning", h,
2✔
396
                                        h.htlcResolution.ClaimOutpoint,
2✔
397
                                        h.htlcExpiry, currentHeight)
2✔
398

2✔
399
                                h.markResolved()
2✔
400

2✔
401
                                if err := h.processFinalHtlcFail(); err != nil {
2✔
402
                                        return nil, err
×
403
                                }
×
404

405
                                report := h.report().resolverReport(
2✔
406
                                        nil,
2✔
407
                                        channeldb.ResolverTypeIncomingHtlc,
2✔
408
                                        channeldb.ResolverOutcomeTimeout,
2✔
409
                                )
2✔
410
                                return nil, h.Checkpoint(h, report)
2✔
411
                        }
412

UNCOV
413
                case <-h.quit:
×
UNCOV
414
                        return nil, errResolverShuttingDown
×
415
                }
416
        }
417
}
418

419
// applyPreimage is a helper function that will populate our internal resolver
420
// with the preimage we learn of. This should be called once the preimage is
421
// revealed so the inner resolver can properly complete its duties. The error
422
// return value indicates whether the preimage was properly applied.
423
func (h *htlcIncomingContestResolver) applyPreimage(
424
        preimage lntypes.Preimage) error {
7✔
425

7✔
426
        // Sanity check to see if this preimage matches our htlc. At this point
7✔
427
        // it should never happen that it does not match.
7✔
428
        if !preimage.Matches(h.htlc.RHash) {
7✔
NEW
429
                return errors.New("preimage does not match hash")
×
NEW
430
        }
×
431

432
        // We may already have the preimage since both the `Launch` and
433
        // `Resolve` methods will look for it.
434
        if h.htlcResolution.Preimage != lntypes.ZeroHash {
9✔
435
                h.log.Debugf("already applied preimage for htlc=%x",
2✔
436
                        h.htlc.RHash)
2✔
437

2✔
438
                return nil
2✔
439
        }
2✔
440

441
        // Update htlcResolution with the matching preimage.
442
        h.htlcResolution.Preimage = preimage
5✔
443

5✔
444
        log.Infof("%T(%v): applied preimage=%v", h,
5✔
445
                h.htlcResolution.ClaimOutpoint, preimage)
5✔
446

5✔
447
        isSecondLevel := h.htlcResolution.SignedSuccessTx != nil
5✔
448

5✔
449
        // If we didn't have to go to the second level to claim (this
5✔
450
        // is the remote commitment transaction), then we don't need to
5✔
451
        // modify our canned witness.
5✔
452
        if !isSecondLevel {
10✔
453
                return nil
5✔
454
        }
5✔
455

NEW
456
        isTaproot := txscript.IsPayToTaproot(
×
NEW
457
                h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
×
NEW
458
        )
×
NEW
459

×
NEW
460
        // If this is our commitment transaction, then we'll need to
×
NEW
461
        // populate the witness for the second-level HTLC transaction.
×
NEW
462
        switch {
×
463
        // For taproot channels, the witness for sweeping with success
464
        // looks like:
465
        //   - <sender sig> <receiver sig> <preimage> <success_script>
466
        //     <control_block>
467
        //
468
        // So we'll insert it at the 3rd index of the witness.
NEW
469
        case isTaproot:
×
NEW
470
                //nolint:ll
×
NEW
471
                h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:]
×
472

473
        // Within the witness for the success transaction, the
474
        // preimage is the 4th element as it looks like:
475
        //
476
        //  * <0> <sender sig> <recvr sig> <preimage> <witness script>
477
        //
478
        // We'll populate it within the witness, as since this
479
        // was a "contest" resolver, we didn't yet know of the
480
        // preimage.
NEW
481
        case !isTaproot:
×
NEW
482
                //nolint:ll
×
NEW
483
                h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
×
484
        }
485

NEW
486
        return nil
×
487
}
488

489
// report returns a report on the resolution state of the contract.
490
func (h *htlcIncomingContestResolver) report() *ContractReport {
5✔
491
        // No locking needed as these values are read-only.
5✔
492

5✔
493
        finalAmt := h.htlc.Amt.ToSatoshis()
5✔
494
        if h.htlcResolution.SignedSuccessTx != nil {
5✔
UNCOV
495
                finalAmt = btcutil.Amount(
×
UNCOV
496
                        h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
×
UNCOV
497
                )
×
UNCOV
498
        }
×
499

500
        return &ContractReport{
5✔
501
                Outpoint:       h.htlcResolution.ClaimOutpoint,
5✔
502
                Type:           ReportOutputIncomingHtlc,
5✔
503
                Amount:         finalAmt,
5✔
504
                MaturityHeight: h.htlcExpiry,
5✔
505
                LimboBalance:   finalAmt,
5✔
506
                Stage:          1,
5✔
507
        }
5✔
508
}
509

510
// Stop signals the resolver to cancel any current resolution processes, and
511
// suspend.
512
//
513
// NOTE: Part of the ContractResolver interface.
UNCOV
514
func (h *htlcIncomingContestResolver) Stop() {
×
NEW
515
        h.log.Debugf("stopping...")
×
NEW
516
        defer h.log.Debugf("stopped")
×
UNCOV
517
        close(h.quit)
×
UNCOV
518
}
×
519

520
// Encode writes an encoded version of the ContractResolver into the passed
521
// Writer.
522
//
523
// NOTE: Part of the ContractResolver interface.
524
func (h *htlcIncomingContestResolver) Encode(w io.Writer) error {
1✔
525
        // We'll first write out the one field unique to this resolver.
1✔
526
        if err := binary.Write(w, endian, h.htlcExpiry); err != nil {
1✔
527
                return err
×
528
        }
×
529

530
        // Then we'll write out our internal resolver.
531
        return h.htlcSuccessResolver.Encode(w)
1✔
532
}
533

534
// newIncomingContestResolverFromReader attempts to decode an encoded ContractResolver
535
// from the passed Reader instance, returning an active ContractResolver
536
// instance.
537
func newIncomingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
538
        *htlcIncomingContestResolver, error) {
1✔
539

1✔
540
        h := &htlcIncomingContestResolver{}
1✔
541

1✔
542
        // We'll first read the one field unique to this resolver.
1✔
543
        if err := binary.Read(r, endian, &h.htlcExpiry); err != nil {
1✔
544
                return nil, err
×
545
        }
×
546

547
        // Then we'll decode our internal resolver.
548
        successResolver, err := newSuccessResolverFromReader(r, resCfg)
1✔
549
        if err != nil {
1✔
550
                return nil, err
×
551
        }
×
552
        h.htlcSuccessResolver = successResolver
1✔
553

1✔
554
        return h, nil
1✔
555
}
556

557
// Supplement adds additional information to the resolver that is required
558
// before Resolve() is called.
559
//
560
// NOTE: Part of the htlcContractResolver interface.
UNCOV
561
func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) {
×
UNCOV
562
        h.htlc = htlc
×
UNCOV
563
}
×
564

565
// SupplementDeadline does nothing for an incoming htlc resolver.
566
//
567
// NOTE: Part of the htlcContractResolver interface.
568
func (h *htlcIncomingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
×
569
}
×
570

571
// decodePayload (re)decodes the hop payload of a received htlc.
572
func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload,
573
        []byte, error) {
16✔
574

16✔
575
        blindingInfo := hop.ReconstructBlindingInfo{
16✔
576
                IncomingAmt:    h.htlc.Amt,
16✔
577
                IncomingExpiry: h.htlc.RefundTimeout,
16✔
578
                BlindingKey:    h.htlc.BlindingPoint,
16✔
579
        }
16✔
580

16✔
581
        onionReader := bytes.NewReader(h.htlc.OnionBlob[:])
16✔
582
        iterator, err := h.OnionProcessor.ReconstructHopIterator(
16✔
583
                onionReader, h.htlc.RHash[:], blindingInfo,
16✔
584
        )
16✔
585
        if err != nil {
16✔
586
                return nil, nil, err
×
587
        }
×
588

589
        payload, _, err := iterator.HopPayload()
16✔
590
        if err != nil {
16✔
591
                return nil, nil, err
×
592
        }
×
593

594
        // Transform onion blob for the next hop.
595
        var onionBlob [lnwire.OnionPacketSize]byte
16✔
596
        buf := bytes.NewBuffer(onionBlob[0:0])
16✔
597
        err = iterator.EncodeNextHop(buf)
16✔
598
        if err != nil {
16✔
599
                return nil, nil, err
×
600
        }
×
601

602
        return payload, onionBlob[:], nil
16✔
603
}
604

605
// A compile time assertion to ensure htlcIncomingContestResolver meets the
606
// ContractResolver interface.
607
var _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
608

609
// findAndapplyPreimage performs a non-blocking read to find the preimage for
610
// the incoming HTLC. If found, it will be applied to the resolver. This method
611
// is used for the resolver to decide whether it wants to transform into a
612
// success resolver during launching.
613
//
614
// NOTE: Since we have two places to query the preimage, we need to check both
615
// the preimage db and the invoice db to look up the preimage.
616
func (h *htlcIncomingContestResolver) findAndapplyPreimage() (bool, error) {
9✔
617
        // Query to see if we already know the preimage.
9✔
618
        preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
9✔
619

9✔
620
        // If the preimage is known, we'll apply it.
9✔
621
        if ok {
11✔
622
                if err := h.applyPreimage(preimage); err != nil {
2✔
NEW
623
                        return false, err
×
NEW
624
                }
×
625

626
                // Successfully applied the preimage, we can now return.
627
                return true, nil
2✔
628
        }
629

630
        // First try to parse the payload.
631
        payload, _, err := h.decodePayload()
7✔
632
        if err != nil {
7✔
NEW
633
                h.log.Errorf("Cannot decode payload of htlc %v", h.HtlcPoint())
×
NEW
634

×
NEW
635
                // If we cannot decode the payload, we will return a nil error
×
NEW
636
                // and let it to be handled in `Resolve`.
×
NEW
637
                return false, nil
×
NEW
638
        }
×
639

640
        // Exit early if this is not the exit hop, which means we are not the
641
        // payment receiver and don't have preimage.
642
        if payload.FwdInfo.NextHop != hop.Exit {
9✔
643
                return false, nil
2✔
644
        }
2✔
645

646
        // Notify registry that we are potentially resolving as an exit hop
647
        // on-chain. If this HTLC indeed pays to an existing invoice, the
648
        // invoice registry will tell us what to do with the HTLC. This is
649
        // identical to HTLC resolution in the link.
650
        circuitKey := models.CircuitKey{
5✔
651
                ChanID: h.ShortChanID,
5✔
652
                HtlcID: h.htlc.HtlcIndex,
5✔
653
        }
5✔
654

5✔
655
        // Try get the resolution - if it doesn't give us a resolution
5✔
656
        // immediately, we'll assume we don't know it yet and let the `Resolve`
5✔
657
        // handle the waiting.
5✔
658
        //
5✔
659
        // NOTE: we use a nil subscriber here and a zero current height as we
5✔
660
        // are only interested in the settle resolution.
5✔
661
        //
5✔
662
        // TODO(yy): move this logic to link and let the preimage be accessed
5✔
663
        // via the preimage beacon.
5✔
664
        resolution, err := h.Registry.NotifyExitHopHtlc(
5✔
665
                h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, 0,
5✔
666
                circuitKey, nil, nil, payload,
5✔
667
        )
5✔
668
        if err != nil {
5✔
NEW
669
                return false, err
×
NEW
670
        }
×
671

672
        res, ok := resolution.(*invoices.HtlcSettleResolution)
5✔
673

5✔
674
        // Exit early if it's not a settle resolution.
5✔
675
        if !ok {
9✔
676
                return false, nil
4✔
677
        }
4✔
678

679
        // Otherwise we have a settle resolution, apply the preimage.
680
        err = h.applyPreimage(res.Preimage)
1✔
681
        if err != nil {
1✔
NEW
682
                return false, err
×
NEW
683
        }
×
684

685
        return true, nil
1✔
686
}
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