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

lightningnetwork / lnd / 12312390362

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

Pull #9343

github

ellemouton
fn: rework the ContextGuard and add tests

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

101853 of 177264 relevant lines covered (57.46%)

24972.93 hits per line

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

69.49
/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 {
×
46

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

×
51
        return &htlcIncomingContestResolver{
×
52
                htlcExpiry:          htlc.RefundTimeout,
×
53
                htlcSuccessResolver: success,
×
54
        }
×
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
// Resolve attempts to resolve this contract. As we don't yet know of the
82
// preimage for the contract, we'll wait for one of two things to happen:
83
//
84
//  1. We learn of the preimage! In this case, we can sweep the HTLC incoming
85
//     and ensure that if this was a multi-hop HTLC we are made whole. In this
86
//     case, an additional ContractResolver will be returned to finish the
87
//     job.
88
//
89
//  2. The HTLC expires. If this happens, then the contract is fully resolved
90
//     as we have no remaining actions left at our disposal.
91
//
92
// NOTE: Part of the ContractResolver interface.
93
func (h *htlcIncomingContestResolver) Resolve(
94
        _ bool) (ContractResolver, error) {
9✔
95

9✔
96
        // If we're already full resolved, then we don't have anything further
9✔
97
        // to do.
9✔
98
        if h.resolved {
9✔
99
                return nil, nil
×
100
        }
×
101

102
        // First try to parse the payload. If that fails, we can stop resolution
103
        // now.
104
        payload, nextHopOnionBlob, err := h.decodePayload()
9✔
105
        if err != nil {
9✔
106
                log.Debugf("ChannelArbitrator(%v): cannot decode payload of "+
×
107
                        "htlc %v", h.ChanPoint, h.HtlcPoint())
×
108

×
109
                // If we've locked in an htlc with an invalid payload on our
×
110
                // commitment tx, we don't need to resolve it. The other party
×
111
                // will time it out and get their funds back. This situation
×
112
                // can present itself when we crash before processRemoteAdds in
×
113
                // the link has ran.
×
114
                h.resolved = true
×
115

×
116
                if err := h.processFinalHtlcFail(); err != nil {
×
117
                        return nil, err
×
118
                }
×
119

120
                // We write a report to disk that indicates we could not decode
121
                // the htlc.
122
                resReport := h.report().resolverReport(
×
123
                        nil, channeldb.ResolverTypeIncomingHtlc,
×
124
                        channeldb.ResolverOutcomeAbandoned,
×
125
                )
×
126
                return nil, h.PutResolverReport(nil, resReport)
×
127
        }
128

129
        // Register for block epochs. After registration, the current height
130
        // will be sent on the channel immediately.
131
        blockEpochs, err := h.Notifier.RegisterBlockEpochNtfn(nil)
9✔
132
        if err != nil {
9✔
133
                return nil, err
×
134
        }
×
135
        defer blockEpochs.Cancel()
9✔
136

9✔
137
        var currentHeight int32
9✔
138
        select {
9✔
139
        case newBlock, ok := <-blockEpochs.Epochs:
9✔
140
                if !ok {
9✔
141
                        return nil, errResolverShuttingDown
×
142
                }
×
143
                currentHeight = newBlock.Height
9✔
144
        case <-h.quit:
×
145
                return nil, errResolverShuttingDown
×
146
        }
147

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

9✔
151
        // We'll first check if this HTLC has been timed out, if so, we can
9✔
152
        // return now and mark ourselves as resolved. If we're past the point of
9✔
153
        // expiry of the HTLC, then at this point the sender can sweep it, so
9✔
154
        // we'll end our lifetime. Here we deliberately forego the chance that
9✔
155
        // the sender doesn't sweep and we already have or will learn the
9✔
156
        // preimage. Otherwise the resolver could potentially stay active
9✔
157
        // indefinitely and the channel will never close properly.
9✔
158
        if uint32(currentHeight) >= h.htlcExpiry {
10✔
159
                // TODO(roasbeef): should also somehow check if outgoing is
1✔
160
                // resolved or not
1✔
161
                //  * may need to hook into the circuit map
1✔
162
                //  * can't timeout before the outgoing has been
1✔
163

1✔
164
                log.Infof("%T(%v): HTLC has timed out (expiry=%v, height=%v), "+
1✔
165
                        "abandoning", h, h.htlcResolution.ClaimOutpoint,
1✔
166
                        h.htlcExpiry, currentHeight)
1✔
167
                h.resolved = true
1✔
168

1✔
169
                if err := h.processFinalHtlcFail(); err != nil {
1✔
170
                        return nil, err
×
171
                }
×
172

173
                // Finally, get our report and checkpoint our resolver with a
174
                // timeout outcome report.
175
                report := h.report().resolverReport(
1✔
176
                        nil, channeldb.ResolverTypeIncomingHtlc,
1✔
177
                        channeldb.ResolverOutcomeTimeout,
1✔
178
                )
1✔
179
                return nil, h.Checkpoint(h, report)
1✔
180
        }
181

182
        // applyPreimage is a helper function that will populate our internal
183
        // resolver with the preimage we learn of. This should be called once
184
        // the preimage is revealed so the inner resolver can properly complete
185
        // its duties. The error return value indicates whether the preimage
186
        // was properly applied.
187
        applyPreimage := func(preimage lntypes.Preimage) error {
12✔
188
                // Sanity check to see if this preimage matches our htlc. At
4✔
189
                // this point it should never happen that it does not match.
4✔
190
                if !preimage.Matches(h.htlc.RHash) {
4✔
191
                        return errors.New("preimage does not match hash")
×
192
                }
×
193

194
                // Update htlcResolution with the matching preimage.
195
                h.htlcResolution.Preimage = preimage
4✔
196

4✔
197
                log.Infof("%T(%v): applied preimage=%v", h,
4✔
198
                        h.htlcResolution.ClaimOutpoint, preimage)
4✔
199

4✔
200
                isSecondLevel := h.htlcResolution.SignedSuccessTx != nil
4✔
201

4✔
202
                // If we didn't have to go to the second level to claim (this
4✔
203
                // is the remote commitment transaction), then we don't need to
4✔
204
                // modify our canned witness.
4✔
205
                if !isSecondLevel {
8✔
206
                        return nil
4✔
207
                }
4✔
208

209
                isTaproot := txscript.IsPayToTaproot(
×
210
                        h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
×
211
                )
×
212

×
213
                // If this is our commitment transaction, then we'll need to
×
214
                // populate the witness for the second-level HTLC transaction.
×
215
                switch {
×
216
                // For taproot channels, the witness for sweeping with success
217
                // looks like:
218
                //   - <sender sig> <receiver sig> <preimage> <success_script>
219
                //     <control_block>
220
                //
221
                // So we'll insert it at the 3rd index of the witness.
222
                case isTaproot:
×
223
                        //nolint:ll
×
224
                        h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:]
×
225

226
                // Within the witness for the success transaction, the
227
                // preimage is the 4th element as it looks like:
228
                //
229
                //  * <0> <sender sig> <recvr sig> <preimage> <witness script>
230
                //
231
                // We'll populate it within the witness, as since this
232
                // was a "contest" resolver, we didn't yet know of the
233
                // preimage.
234
                case !isTaproot:
×
235
                        h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
×
236
                }
237

238
                return nil
×
239
        }
240

241
        // Define a closure to process htlc resolutions either directly or
242
        // triggered by future notifications.
243
        processHtlcResolution := func(e invoices.HtlcResolution) (
8✔
244
                ContractResolver, error) {
12✔
245

4✔
246
                // Take action based on the type of resolution we have
4✔
247
                // received.
4✔
248
                switch resolution := e.(type) {
4✔
249
                // If the htlc resolution was a settle, apply the
250
                // preimage and return a success resolver.
251
                case *invoices.HtlcSettleResolution:
2✔
252
                        err := applyPreimage(resolution.Preimage)
2✔
253
                        if err != nil {
2✔
254
                                return nil, err
×
255
                        }
×
256

257
                        return h.htlcSuccessResolver, nil
2✔
258

259
                // If the htlc was failed, mark the htlc as
260
                // resolved.
261
                case *invoices.HtlcFailResolution:
2✔
262
                        log.Infof("%T(%v): Exit hop HTLC canceled "+
2✔
263
                                "(expiry=%v, height=%v), abandoning", h,
2✔
264
                                h.htlcResolution.ClaimOutpoint,
2✔
265
                                h.htlcExpiry, currentHeight)
2✔
266

2✔
267
                        h.resolved = true
2✔
268

2✔
269
                        if err := h.processFinalHtlcFail(); err != nil {
2✔
270
                                return nil, err
×
271
                        }
×
272

273
                        // Checkpoint our resolver with an abandoned outcome
274
                        // because we take no further action on this htlc.
275
                        report := h.report().resolverReport(
2✔
276
                                nil, channeldb.ResolverTypeIncomingHtlc,
2✔
277
                                channeldb.ResolverOutcomeAbandoned,
2✔
278
                        )
2✔
279
                        return nil, h.Checkpoint(h, report)
2✔
280

281
                // Error if the resolution type is unknown, we are only
282
                // expecting settles and fails.
283
                default:
×
284
                        return nil, fmt.Errorf("unknown resolution"+
×
285
                                " type: %v", e)
×
286
                }
287
        }
288

289
        var (
8✔
290
                hodlChan       <-chan interface{}
8✔
291
                witnessUpdates <-chan lntypes.Preimage
8✔
292
        )
8✔
293
        if payload.FwdInfo.NextHop == hop.Exit {
13✔
294
                // Create a buffered hodl chan to prevent deadlock.
5✔
295
                hodlQueue := queue.NewConcurrentQueue(10)
5✔
296
                hodlQueue.Start()
5✔
297

5✔
298
                hodlChan = hodlQueue.ChanOut()
5✔
299

5✔
300
                // Notify registry that we are potentially resolving as an exit
5✔
301
                // hop on-chain. If this HTLC indeed pays to an existing
5✔
302
                // invoice, the invoice registry will tell us what to do with
5✔
303
                // the HTLC. This is identical to HTLC resolution in the link.
5✔
304
                circuitKey := models.CircuitKey{
5✔
305
                        ChanID: h.ShortChanID,
5✔
306
                        HtlcID: h.htlc.HtlcIndex,
5✔
307
                }
5✔
308

5✔
309
                resolution, err := h.Registry.NotifyExitHopHtlc(
5✔
310
                        h.htlc.RHash, h.htlc.Amt, h.htlcExpiry, currentHeight,
5✔
311
                        circuitKey, hodlQueue.ChanIn(), nil, payload,
5✔
312
                )
5✔
313
                if err != nil {
5✔
314
                        return nil, err
×
315
                }
×
316

317
                defer func() {
10✔
318
                        h.Registry.HodlUnsubscribeAll(hodlQueue.ChanIn())
5✔
319

5✔
320
                        hodlQueue.Stop()
5✔
321
                }()
5✔
322

323
                // Take action based on the resolution we received. If the htlc
324
                // was settled, or a htlc for a known invoice failed we can
325
                // resolve it directly. If the resolution is nil, the htlc was
326
                // neither accepted nor failed, so we cannot take action yet.
327
                switch res := resolution.(type) {
5✔
328
                case *invoices.HtlcFailResolution:
1✔
329
                        // In the case where the htlc failed, but the invoice
1✔
330
                        // was known to the registry, we can directly resolve
1✔
331
                        // the htlc.
1✔
332
                        if res.Outcome != invoices.ResultInvoiceNotFound {
2✔
333
                                return processHtlcResolution(resolution)
1✔
334
                        }
1✔
335

336
                // If we settled the htlc, we can resolve it.
337
                case *invoices.HtlcSettleResolution:
1✔
338
                        return processHtlcResolution(resolution)
1✔
339

340
                // If the resolution is nil, the htlc was neither settled nor
341
                // failed so we cannot take action at present.
342
                case nil:
3✔
343

344
                default:
×
345
                        return nil, fmt.Errorf("unknown htlc resolution type: %T",
×
346
                                resolution)
×
347
                }
348
        } else {
3✔
349
                // If the HTLC hasn't expired yet, then we may still be able to
3✔
350
                // claim it if we learn of the pre-image, so we'll subscribe to
3✔
351
                // the preimage database to see if it turns up, or the HTLC
3✔
352
                // times out.
3✔
353
                //
3✔
354
                // NOTE: This is done BEFORE opportunistically querying the db,
3✔
355
                // to ensure the preimage can't be delivered between querying
3✔
356
                // and registering for the preimage subscription.
3✔
357
                preimageSubscription, err := h.PreimageDB.SubscribeUpdates(
3✔
358
                        h.htlcSuccessResolver.ShortChanID, &h.htlc,
3✔
359
                        payload, nextHopOnionBlob,
3✔
360
                )
3✔
361
                if err != nil {
3✔
362
                        return nil, err
×
363
                }
×
364
                defer preimageSubscription.CancelSubscription()
3✔
365

3✔
366
                // With the epochs and preimage subscriptions initialized, we'll
3✔
367
                // query to see if we already know the preimage.
3✔
368
                preimage, ok := h.PreimageDB.LookupPreimage(h.htlc.RHash)
3✔
369
                if ok {
4✔
370
                        // If we do, then this means we can claim the HTLC!
1✔
371
                        // However, we don't know how to ourselves, so we'll
1✔
372
                        // return our inner resolver which has the knowledge to
1✔
373
                        // do so.
1✔
374
                        if err := applyPreimage(preimage); err != nil {
1✔
375
                                return nil, err
×
376
                        }
×
377

378
                        return h.htlcSuccessResolver, nil
1✔
379
                }
380

381
                witnessUpdates = preimageSubscription.WitnessUpdates
2✔
382
        }
383

384
        for {
11✔
385
                select {
6✔
386
                case preimage := <-witnessUpdates:
1✔
387
                        // We received a new preimage, but we need to ignore
1✔
388
                        // all except the preimage we are waiting for.
1✔
389
                        if !preimage.Matches(h.htlc.RHash) {
1✔
390
                                continue
×
391
                        }
392

393
                        if err := applyPreimage(preimage); err != nil {
1✔
394
                                return nil, err
×
395
                        }
×
396

397
                        // We've learned of the preimage and this information
398
                        // has been added to our inner resolver. We return it so
399
                        // it can continue contract resolution.
400
                        return h.htlcSuccessResolver, nil
1✔
401

402
                case hodlItem := <-hodlChan:
2✔
403
                        htlcResolution := hodlItem.(invoices.HtlcResolution)
2✔
404
                        return processHtlcResolution(htlcResolution)
2✔
405

406
                case newBlock, ok := <-blockEpochs.Epochs:
3✔
407
                        if !ok {
3✔
408
                                return nil, errResolverShuttingDown
×
409
                        }
×
410

411
                        // If this new height expires the HTLC, then this means
412
                        // we never found out the preimage, so we can mark
413
                        // resolved and exit.
414
                        newHeight := uint32(newBlock.Height)
3✔
415
                        if newHeight >= h.htlcExpiry {
5✔
416
                                log.Infof("%T(%v): HTLC has timed out "+
2✔
417
                                        "(expiry=%v, height=%v), abandoning", h,
2✔
418
                                        h.htlcResolution.ClaimOutpoint,
2✔
419
                                        h.htlcExpiry, currentHeight)
2✔
420
                                h.resolved = true
2✔
421

2✔
422
                                if err := h.processFinalHtlcFail(); err != nil {
2✔
423
                                        return nil, err
×
424
                                }
×
425

426
                                report := h.report().resolverReport(
2✔
427
                                        nil,
2✔
428
                                        channeldb.ResolverTypeIncomingHtlc,
2✔
429
                                        channeldb.ResolverOutcomeTimeout,
2✔
430
                                )
2✔
431
                                return nil, h.Checkpoint(h, report)
2✔
432
                        }
433

434
                case <-h.quit:
×
435
                        return nil, errResolverShuttingDown
×
436
                }
437
        }
438
}
439

440
// report returns a report on the resolution state of the contract.
441
func (h *htlcIncomingContestResolver) report() *ContractReport {
5✔
442
        // No locking needed as these values are read-only.
5✔
443

5✔
444
        finalAmt := h.htlc.Amt.ToSatoshis()
5✔
445
        if h.htlcResolution.SignedSuccessTx != nil {
5✔
446
                finalAmt = btcutil.Amount(
×
447
                        h.htlcResolution.SignedSuccessTx.TxOut[0].Value,
×
448
                )
×
449
        }
×
450

451
        return &ContractReport{
5✔
452
                Outpoint:       h.htlcResolution.ClaimOutpoint,
5✔
453
                Type:           ReportOutputIncomingHtlc,
5✔
454
                Amount:         finalAmt,
5✔
455
                MaturityHeight: h.htlcExpiry,
5✔
456
                LimboBalance:   finalAmt,
5✔
457
                Stage:          1,
5✔
458
        }
5✔
459
}
460

461
// Stop signals the resolver to cancel any current resolution processes, and
462
// suspend.
463
//
464
// NOTE: Part of the ContractResolver interface.
465
func (h *htlcIncomingContestResolver) Stop() {
×
466
        close(h.quit)
×
467
}
×
468

469
// IsResolved returns true if the stored state in the resolve is fully
470
// resolved. In this case the target output can be forgotten.
471
//
472
// NOTE: Part of the ContractResolver interface.
473
func (h *htlcIncomingContestResolver) IsResolved() bool {
×
474
        return h.resolved
×
475
}
×
476

477
// Encode writes an encoded version of the ContractResolver into the passed
478
// Writer.
479
//
480
// NOTE: Part of the ContractResolver interface.
481
func (h *htlcIncomingContestResolver) Encode(w io.Writer) error {
1✔
482
        // We'll first write out the one field unique to this resolver.
1✔
483
        if err := binary.Write(w, endian, h.htlcExpiry); err != nil {
1✔
484
                return err
×
485
        }
×
486

487
        // Then we'll write out our internal resolver.
488
        return h.htlcSuccessResolver.Encode(w)
1✔
489
}
490

491
// newIncomingContestResolverFromReader attempts to decode an encoded ContractResolver
492
// from the passed Reader instance, returning an active ContractResolver
493
// instance.
494
func newIncomingContestResolverFromReader(r io.Reader, resCfg ResolverConfig) (
495
        *htlcIncomingContestResolver, error) {
1✔
496

1✔
497
        h := &htlcIncomingContestResolver{}
1✔
498

1✔
499
        // We'll first read the one field unique to this resolver.
1✔
500
        if err := binary.Read(r, endian, &h.htlcExpiry); err != nil {
1✔
501
                return nil, err
×
502
        }
×
503

504
        // Then we'll decode our internal resolver.
505
        successResolver, err := newSuccessResolverFromReader(r, resCfg)
1✔
506
        if err != nil {
1✔
507
                return nil, err
×
508
        }
×
509
        h.htlcSuccessResolver = successResolver
1✔
510

1✔
511
        return h, nil
1✔
512
}
513

514
// Supplement adds additional information to the resolver that is required
515
// before Resolve() is called.
516
//
517
// NOTE: Part of the htlcContractResolver interface.
518
func (h *htlcIncomingContestResolver) Supplement(htlc channeldb.HTLC) {
×
519
        h.htlc = htlc
×
520
}
×
521

522
// SupplementDeadline does nothing for an incoming htlc resolver.
523
//
524
// NOTE: Part of the htlcContractResolver interface.
525
func (h *htlcIncomingContestResolver) SupplementDeadline(_ fn.Option[int32]) {
×
526
}
×
527

528
// decodePayload (re)decodes the hop payload of a received htlc.
529
func (h *htlcIncomingContestResolver) decodePayload() (*hop.Payload,
530
        []byte, error) {
9✔
531

9✔
532
        blindingInfo := hop.ReconstructBlindingInfo{
9✔
533
                IncomingAmt:    h.htlc.Amt,
9✔
534
                IncomingExpiry: h.htlc.RefundTimeout,
9✔
535
                BlindingKey:    h.htlc.BlindingPoint,
9✔
536
        }
9✔
537

9✔
538
        onionReader := bytes.NewReader(h.htlc.OnionBlob[:])
9✔
539
        iterator, err := h.OnionProcessor.ReconstructHopIterator(
9✔
540
                onionReader, h.htlc.RHash[:], blindingInfo,
9✔
541
        )
9✔
542
        if err != nil {
9✔
543
                return nil, nil, err
×
544
        }
×
545

546
        payload, _, err := iterator.HopPayload()
9✔
547
        if err != nil {
9✔
548
                return nil, nil, err
×
549
        }
×
550

551
        // Transform onion blob for the next hop.
552
        var onionBlob [lnwire.OnionPacketSize]byte
9✔
553
        buf := bytes.NewBuffer(onionBlob[0:0])
9✔
554
        err = iterator.EncodeNextHop(buf)
9✔
555
        if err != nil {
9✔
556
                return nil, nil, err
×
557
        }
×
558

559
        return payload, onionBlob[:], nil
9✔
560
}
561

562
// A compile time assertion to ensure htlcIncomingContestResolver meets the
563
// ContractResolver interface.
564
var _ htlcContractResolver = (*htlcIncomingContestResolver)(nil)
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