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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

1.54 hits per line

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

81.17
/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,
45
        htlc channeldb.HTLC, resCfg ResolverConfig) *htlcIncomingContestResolver {
3✔
46

3✔
47
        success := newSuccessResolver(
3✔
48
                res, broadcastHeight, htlc, resCfg,
3✔
49
        )
3✔
50

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

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

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

3✔
78
        return nil
3✔
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 {
3✔
84
        // NOTE: we don't mark this resolver as launched as the inner resolver
3✔
85
        // will set it when it's launched.
3✔
86
        if h.isLaunched() {
6✔
87
                h.log.Tracef("already launched")
3✔
88
                return nil
3✔
89
        }
3✔
90

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

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

99
        // No preimage found, leave it to be handled by the resolver.
100
        if !applied {
6✔
101
                return nil
3✔
102
        }
3✔
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) {
3✔
125
        // If we're already full resolved, then we don't have anything further
3✔
126
        // to do.
3✔
127
        if h.IsResolved() {
3✔
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()
3✔
135
        if err != nil {
3✔
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.
×
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)
3✔
161
        if err != nil {
3✔
162
                return nil, err
×
163
        }
×
164
        defer blockEpochs.Cancel()
3✔
165

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

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

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

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

3✔
198
                if err := h.processFinalHtlcFail(); err != nil {
3✔
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(
3✔
205
                        nil, channeldb.ResolverTypeIncomingHtlc,
3✔
206
                        channeldb.ResolverOutcomeTimeout,
3✔
207
                )
3✔
208
                return nil, h.Checkpoint(h, report)
3✔
209
        }
210

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

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

227
                        return h.htlcSuccessResolver, nil
3✔
228

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

3✔
237
                        h.markResolved()
3✔
238

3✔
239
                        if err := h.processFinalHtlcFail(); err != nil {
3✔
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(
3✔
246
                                nil, channeldb.ResolverTypeIncomingHtlc,
3✔
247
                                channeldb.ResolverOutcomeAbandoned,
3✔
248
                        )
3✔
249
                        return nil, h.Checkpoint(h, report)
3✔
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 (
3✔
260
                hodlChan       <-chan interface{}
3✔
261
                witnessUpdates <-chan lntypes.Preimage
3✔
262
        )
3✔
263
        if payload.FwdInfo.NextHop == hop.Exit {
6✔
264
                // Create a buffered hodl chan to prevent deadlock.
3✔
265
                hodlQueue := queue.NewConcurrentQueue(10)
3✔
266
                hodlQueue.Start()
3✔
267

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

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

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

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

3✔
291
                defer func() {
6✔
292
                        h.Registry.HodlUnsubscribeAll(hodlQueue.ChanIn())
3✔
293

3✔
294
                        hodlQueue.Stop()
3✔
295
                }()
3✔
296

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

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

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

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

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

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

354
                        return h.htlcSuccessResolver, nil
3✔
355
                }
356

357
                witnessUpdates = preimageSubscription.WitnessUpdates
3✔
358
        }
359

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

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

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

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

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

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

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

3✔
400
                                h.markResolved()
3✔
401

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

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

414
                case <-h.quit:
3✔
415
                        return nil, errResolverShuttingDown
3✔
416
                }
417
        }
418
}
419

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

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

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

3✔
439
                return nil
3✔
440
        }
3✔
441

442
        // Update htlcResolution with the matching preimage.
443
        h.htlcResolution.Preimage = preimage
3✔
444

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

3✔
448
        isSecondLevel := h.htlcResolution.SignedSuccessTx != nil
3✔
449

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

457
        isTaproot := txscript.IsPayToTaproot(
3✔
458
                h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
3✔
459
        )
3✔
460

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

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

487
        return nil
3✔
488
}
489

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

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

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

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

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

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

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

3✔
541
        h := &htlcIncomingContestResolver{}
3✔
542

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

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

3✔
555
        return h, nil
3✔
556
}
557

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

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

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

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

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

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

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

603
        return payload, onionBlob[:], nil
3✔
604
}
605

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

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

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

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

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

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

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

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

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

673
        res, ok := resolution.(*invoices.HtlcSettleResolution)
3✔
674

3✔
675
        // Exit early if it's not a settle resolution.
3✔
676
        if !ok {
6✔
677
                return false, nil
3✔
678
        }
3✔
679

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

686
        return true, nil
3✔
687
}
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