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

lightningnetwork / lnd / 12199391122

06 Dec 2024 01:10PM UTC coverage: 49.807% (-9.1%) from 58.933%
12199391122

push

github

web-flow
Merge pull request #9337 from Guayaba221/patch-1

chore: fix typo in ruby.md

100137 of 201051 relevant lines covered (49.81%)

2.07 hits per line

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

80.23
/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"
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 {
4✔
46

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

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

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

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

4✔
78
        return nil
4✔
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) {
4✔
95

4✔
96
        // If we're already full resolved, then we don't have anything further
4✔
97
        // to do.
4✔
98
        if h.resolved {
4✔
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()
4✔
105
        if err != nil {
4✔
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)
4✔
132
        if err != nil {
4✔
133
                return nil, err
×
134
        }
×
135
        defer blockEpochs.Cancel()
4✔
136

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

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

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

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

4✔
169
                if err := h.processFinalHtlcFail(); err != nil {
4✔
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(
4✔
176
                        nil, channeldb.ResolverTypeIncomingHtlc,
4✔
177
                        channeldb.ResolverOutcomeTimeout,
4✔
178
                )
4✔
179
                return nil, h.Checkpoint(h, report)
4✔
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 {
8✔
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(
4✔
210
                        h.htlcResolution.SignedSuccessTx.TxOut[0].PkScript,
4✔
211
                )
4✔
212

4✔
213
                // If this is our commitment transaction, then we'll need to
4✔
214
                // populate the witness for the second-level HTLC transaction.
4✔
215
                switch {
4✔
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:
4✔
223
                        //nolint:ll
4✔
224
                        h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[2] = preimage[:]
4✔
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:
4✔
235
                        h.htlcResolution.SignedSuccessTx.TxIn[0].Witness[3] = preimage[:]
4✔
236
                }
237

238
                return nil
4✔
239
        }
240

241
        // Define a closure to process htlc resolutions either directly or
242
        // triggered by future notifications.
243
        processHtlcResolution := func(e invoices.HtlcResolution) (
4✔
244
                ContractResolver, error) {
8✔
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:
4✔
252
                        err := applyPreimage(resolution.Preimage)
4✔
253
                        if err != nil {
4✔
254
                                return nil, err
×
255
                        }
×
256

257
                        return h.htlcSuccessResolver, nil
4✔
258

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

4✔
267
                        h.resolved = true
4✔
268

4✔
269
                        if err := h.processFinalHtlcFail(); err != nil {
4✔
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(
4✔
276
                                nil, channeldb.ResolverTypeIncomingHtlc,
4✔
277
                                channeldb.ResolverOutcomeAbandoned,
4✔
278
                        )
4✔
279
                        return nil, h.Checkpoint(h, report)
4✔
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 (
4✔
290
                hodlChan       <-chan interface{}
4✔
291
                witnessUpdates <-chan lntypes.Preimage
4✔
292
        )
4✔
293
        if payload.FwdInfo.NextHop == hop.Exit {
8✔
294
                // Create a buffered hodl chan to prevent deadlock.
4✔
295
                hodlQueue := queue.NewConcurrentQueue(10)
4✔
296
                hodlQueue.Start()
4✔
297

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

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

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

317
                defer func() {
8✔
318
                        h.Registry.HodlUnsubscribeAll(hodlQueue.ChanIn())
4✔
319

4✔
320
                        hodlQueue.Stop()
4✔
321
                }()
4✔
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) {
4✔
328
                case *invoices.HtlcFailResolution:
4✔
329
                        // In the case where the htlc failed, but the invoice
4✔
330
                        // was known to the registry, we can directly resolve
4✔
331
                        // the htlc.
4✔
332
                        if res.Outcome != invoices.ResultInvoiceNotFound {
4✔
333
                                return processHtlcResolution(resolution)
×
334
                        }
×
335

336
                // If we settled the htlc, we can resolve it.
337
                case *invoices.HtlcSettleResolution:
4✔
338
                        return processHtlcResolution(resolution)
4✔
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:
4✔
343

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

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

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

381
                witnessUpdates = preimageSubscription.WitnessUpdates
4✔
382
        }
383

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

393
                        if err := applyPreimage(preimage); err != nil {
4✔
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
4✔
401

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

406
                case newBlock, ok := <-blockEpochs.Epochs:
4✔
407
                        if !ok {
4✔
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)
4✔
415
                        if newHeight >= h.htlcExpiry {
8✔
416
                                log.Infof("%T(%v): HTLC has timed out "+
4✔
417
                                        "(expiry=%v, height=%v), abandoning", h,
4✔
418
                                        h.htlcResolution.ClaimOutpoint,
4✔
419
                                        h.htlcExpiry, currentHeight)
4✔
420
                                h.resolved = true
4✔
421

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

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

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

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

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

451
        return &ContractReport{
4✔
452
                Outpoint:       h.htlcResolution.ClaimOutpoint,
4✔
453
                Type:           ReportOutputIncomingHtlc,
4✔
454
                Amount:         finalAmt,
4✔
455
                MaturityHeight: h.htlcExpiry,
4✔
456
                LimboBalance:   finalAmt,
4✔
457
                Stage:          1,
4✔
458
        }
4✔
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() {
4✔
466
        close(h.quit)
4✔
467
}
4✔
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 {
4✔
474
        return h.resolved
4✔
475
}
4✔
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 {
4✔
482
        // We'll first write out the one field unique to this resolver.
4✔
483
        if err := binary.Write(w, endian, h.htlcExpiry); err != nil {
4✔
484
                return err
×
485
        }
×
486

487
        // Then we'll write out our internal resolver.
488
        return h.htlcSuccessResolver.Encode(w)
4✔
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) {
4✔
496

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

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

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

4✔
511
        return h, nil
4✔
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) {
4✔
519
        h.htlc = htlc
4✔
520
}
4✔
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) {
4✔
531

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

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

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

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

559
        return payload, onionBlob[:], nil
4✔
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