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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

65.32
/htlcswitch/interceptable_switch.go
1
package htlcswitch
2

3
import (
4
        "crypto/sha256"
5
        "fmt"
6
        "sync"
7

8
        "github.com/go-errors/errors"
9
        "github.com/lightningnetwork/lnd/chainntnfs"
10
        "github.com/lightningnetwork/lnd/channeldb/models"
11
        "github.com/lightningnetwork/lnd/htlcswitch/hop"
12
        "github.com/lightningnetwork/lnd/lntypes"
13
        "github.com/lightningnetwork/lnd/lnwire"
14
)
15

16
var (
17
        // ErrFwdNotExists is an error returned when the caller tries to resolve
18
        // a forward that doesn't exist anymore.
19
        ErrFwdNotExists = errors.New("forward does not exist")
20

21
        // ErrUnsupportedFailureCode when processing of an unsupported failure
22
        // code is attempted.
23
        ErrUnsupportedFailureCode = errors.New("unsupported failure code")
24

25
        errBlockStreamStopped = errors.New("block epoch stream stopped")
26
)
27

28
// InterceptableSwitch is an implementation of ForwardingSwitch interface.
29
// This implementation is used like a proxy that wraps the switch and
30
// intercepts forward requests. A reference to the Switch is held in order
31
// to communicate back the interception result where the options are:
32
// Resume - forwards the original request to the switch as is.
33
// Settle - routes UpdateFulfillHTLC to the originating link.
34
// Fail - routes UpdateFailHTLC to the originating link.
35
type InterceptableSwitch struct {
36
        // htlcSwitch is the underline switch
37
        htlcSwitch *Switch
38

39
        // intercepted is where we stream all intercepted packets coming from
40
        // the switch.
41
        intercepted chan *interceptedPackets
42

43
        // resolutionChan is where we stream all responses coming from the
44
        // interceptor client.
45
        resolutionChan chan *fwdResolution
46

47
        onchainIntercepted chan InterceptedForward
48

49
        // interceptorRegistration is a channel that we use to synchronize
50
        // client connect and disconnect.
51
        interceptorRegistration chan ForwardInterceptor
52

53
        // requireInterceptor indicates whether processing should block if no
54
        // interceptor is connected.
55
        requireInterceptor bool
56

57
        // interceptor is the handler for intercepted packets.
58
        interceptor ForwardInterceptor
59

60
        // heldHtlcSet keeps track of outstanding intercepted forwards.
61
        heldHtlcSet *heldHtlcSet
62

63
        // cltvRejectDelta defines the number of blocks before the expiry of the
64
        // htlc where we no longer intercept it and instead cancel it back.
65
        cltvRejectDelta uint32
66

67
        // cltvInterceptDelta defines the number of blocks before the expiry of
68
        // the htlc where we don't intercept anymore. This value must be greater
69
        // than CltvRejectDelta, because we don't want to offer htlcs to the
70
        // interceptor client for which there is no time left to resolve them
71
        // anymore.
72
        cltvInterceptDelta uint32
73

74
        // notifier is an instance of a chain notifier that we'll use to signal
75
        // the switch when a new block has arrived.
76
        notifier chainntnfs.ChainNotifier
77

78
        // blockEpochStream is an active block epoch event stream backed by an
79
        // active ChainNotifier instance. This will be used to retrieve the
80
        // latest height of the chain.
81
        blockEpochStream *chainntnfs.BlockEpochEvent
82

83
        // currentHeight is the currently best known height.
84
        currentHeight int32
85

86
        wg   sync.WaitGroup
87
        quit chan struct{}
88
}
89

90
type interceptedPackets struct {
91
        packets  []*htlcPacket
92
        linkQuit chan struct{}
93
        isReplay bool
94
}
95

96
// FwdAction defines the various resolution types.
97
type FwdAction int
98

99
const (
100
        // FwdActionResume forwards the intercepted packet to the switch.
101
        FwdActionResume FwdAction = iota
102

103
        // FwdActionSettle settles the intercepted packet with a preimage.
104
        FwdActionSettle
105

106
        // FwdActionFail fails the intercepted packet back to the sender.
107
        FwdActionFail
108
)
109

110
// FwdResolution defines the action to be taken on an intercepted packet.
111
type FwdResolution struct {
112
        // Key is the incoming circuit key of the htlc.
113
        Key models.CircuitKey
114

115
        // Action is the action to take on the intercepted htlc.
116
        Action FwdAction
117

118
        // Preimage is the preimage that is to be used for settling if Action is
119
        // FwdActionSettle.
120
        Preimage lntypes.Preimage
121

122
        // FailureMessage is the encrypted failure message that is to be passed
123
        // back to the sender if action is FwdActionFail.
124
        FailureMessage []byte
125

126
        // FailureCode is the failure code that is to be passed back to the
127
        // sender if action is FwdActionFail.
128
        FailureCode lnwire.FailCode
129
}
130

131
type fwdResolution struct {
132
        resolution *FwdResolution
133
        errChan    chan error
134
}
135

136
// InterceptableSwitchConfig contains the configuration of InterceptableSwitch.
137
type InterceptableSwitchConfig struct {
138
        // Switch is a reference to the actual switch implementation that
139
        // packets get sent to on resume.
140
        Switch *Switch
141

142
        // Notifier is an instance of a chain notifier that we'll use to signal
143
        // the switch when a new block has arrived.
144
        Notifier chainntnfs.ChainNotifier
145

146
        // CltvRejectDelta defines the number of blocks before the expiry of the
147
        // htlc where we auto-fail an intercepted htlc to prevent channel
148
        // force-closure.
149
        CltvRejectDelta uint32
150

151
        // CltvInterceptDelta defines the number of blocks before the expiry of
152
        // the htlc where we don't intercept anymore. This value must be greater
153
        // than CltvRejectDelta, because we don't want to offer htlcs to the
154
        // interceptor client for which there is no time left to resolve them
155
        // anymore.
156
        CltvInterceptDelta uint32
157

158
        // RequireInterceptor indicates whether processing should block if no
159
        // interceptor is connected.
160
        RequireInterceptor bool
161
}
162

163
// NewInterceptableSwitch returns an instance of InterceptableSwitch.
164
func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) (
165
        *InterceptableSwitch, error) {
3✔
166

3✔
167
        if cfg.CltvInterceptDelta <= cfg.CltvRejectDelta {
3✔
168
                return nil, fmt.Errorf("cltv intercept delta %v not greater "+
×
169
                        "than cltv reject delta %v",
×
170
                        cfg.CltvInterceptDelta, cfg.CltvRejectDelta)
×
171
        }
×
172

173
        return &InterceptableSwitch{
3✔
174
                htlcSwitch:              cfg.Switch,
3✔
175
                intercepted:             make(chan *interceptedPackets),
3✔
176
                onchainIntercepted:      make(chan InterceptedForward),
3✔
177
                interceptorRegistration: make(chan ForwardInterceptor),
3✔
178
                heldHtlcSet:             newHeldHtlcSet(),
3✔
179
                resolutionChan:          make(chan *fwdResolution),
3✔
180
                requireInterceptor:      cfg.RequireInterceptor,
3✔
181
                cltvRejectDelta:         cfg.CltvRejectDelta,
3✔
182
                cltvInterceptDelta:      cfg.CltvInterceptDelta,
3✔
183
                notifier:                cfg.Notifier,
3✔
184

3✔
185
                quit: make(chan struct{}),
3✔
186
        }, nil
3✔
187
}
188

189
// SetInterceptor sets the ForwardInterceptor to be used. A nil argument
190
// unregisters the current interceptor.
191
func (s *InterceptableSwitch) SetInterceptor(
192
        interceptor ForwardInterceptor) {
3✔
193

3✔
194
        // Synchronize setting the handler with the main loop to prevent race
3✔
195
        // conditions.
3✔
196
        select {
3✔
197
        case s.interceptorRegistration <- interceptor:
3✔
198

199
        case <-s.quit:
×
200
        }
201
}
202

203
func (s *InterceptableSwitch) Start() error {
3✔
204
        blockEpochStream, err := s.notifier.RegisterBlockEpochNtfn(nil)
3✔
205
        if err != nil {
3✔
206
                return err
×
207
        }
×
208
        s.blockEpochStream = blockEpochStream
3✔
209

3✔
210
        s.wg.Add(1)
3✔
211
        go func() {
6✔
212
                defer s.wg.Done()
3✔
213

3✔
214
                err := s.run()
3✔
215
                if err != nil {
6✔
216
                        log.Errorf("InterceptableSwitch stopped: %v", err)
3✔
217
                }
3✔
218
        }()
219

220
        return nil
3✔
221
}
222

223
func (s *InterceptableSwitch) Stop() error {
×
224
        close(s.quit)
×
225
        s.wg.Wait()
×
226

×
227
        s.blockEpochStream.Cancel()
×
228

×
229
        return nil
×
230
}
×
231

232
func (s *InterceptableSwitch) run() error {
3✔
233
        // The block epoch stream will immediately stream the current height.
3✔
234
        // Read it out here.
3✔
235
        select {
3✔
236
        case currentBlock, ok := <-s.blockEpochStream.Epochs:
3✔
237
                if !ok {
3✔
238
                        return errBlockStreamStopped
×
239
                }
×
240
                s.currentHeight = currentBlock.Height
3✔
241

242
        case <-s.quit:
×
243
                return nil
×
244
        }
245

246
        log.Debugf("InterceptableSwitch running: height=%v, "+
3✔
247
                "requireInterceptor=%v", s.currentHeight, s.requireInterceptor)
3✔
248

3✔
249
        for {
6✔
250
                select {
3✔
251
                // An interceptor registration or de-registration came in.
252
                case interceptor := <-s.interceptorRegistration:
3✔
253
                        s.setInterceptor(interceptor)
3✔
254

255
                case packets := <-s.intercepted:
3✔
256
                        var notIntercepted []*htlcPacket
3✔
257
                        for _, p := range packets.packets {
6✔
258
                                intercepted, err := s.interceptForward(
3✔
259
                                        p, packets.isReplay,
3✔
260
                                )
3✔
261
                                if err != nil {
3✔
262
                                        return err
×
263
                                }
×
264

265
                                if !intercepted {
6✔
266
                                        notIntercepted = append(
3✔
267
                                                notIntercepted, p,
3✔
268
                                        )
3✔
269
                                }
3✔
270
                        }
271
                        err := s.htlcSwitch.ForwardPackets(
3✔
272
                                packets.linkQuit, notIntercepted...,
3✔
273
                        )
3✔
274
                        if err != nil {
3✔
275
                                log.Errorf("Cannot forward packets: %v", err)
×
276
                        }
×
277

278
                case fwd := <-s.onchainIntercepted:
3✔
279
                        // For on-chain interceptions, we don't know if it has
3✔
280
                        // already been offered before. This information is in
3✔
281
                        // the forwarding package which isn't easily accessible
3✔
282
                        // from contractcourt. It is likely though that it was
3✔
283
                        // already intercepted in the off-chain flow. And even
3✔
284
                        // if not, it is safe to signal replay so that we won't
3✔
285
                        // unexpectedly skip over this htlc.
3✔
286
                        if _, err := s.forward(fwd, true); err != nil {
3✔
287
                                return err
×
288
                        }
×
289

290
                case res := <-s.resolutionChan:
3✔
291
                        res.errChan <- s.resolve(res.resolution)
3✔
292

293
                case currentBlock, ok := <-s.blockEpochStream.Epochs:
3✔
294
                        if !ok {
6✔
295
                                return errBlockStreamStopped
3✔
296
                        }
3✔
297

298
                        s.currentHeight = currentBlock.Height
3✔
299

3✔
300
                        // A new block is appended. Fail any held htlcs that
3✔
301
                        // expire at this height to prevent channel force-close.
3✔
302
                        s.failExpiredHtlcs()
3✔
303

304
                case <-s.quit:
×
305
                        return nil
×
306
                }
307
        }
308
}
309

310
func (s *InterceptableSwitch) failExpiredHtlcs() {
3✔
311
        s.heldHtlcSet.popAutoFails(
3✔
312
                uint32(s.currentHeight),
3✔
313
                func(fwd InterceptedForward) {
3✔
314
                        err := fwd.FailWithCode(
×
315
                                lnwire.CodeTemporaryChannelFailure,
×
316
                        )
×
317
                        if err != nil {
×
318
                                log.Errorf("Cannot fail packet: %v", err)
×
319
                        }
×
320
                },
321
        )
322
}
323

324
func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) {
3✔
325
        err := s.interceptor(fwd.Packet())
3✔
326
        if err != nil {
3✔
327
                // Only log the error. If we couldn't send the packet, we assume
×
328
                // that the interceptor will reconnect so that we can retry.
×
329
                log.Debugf("Interceptor cannot handle forward: %v", err)
×
330
        }
×
331
}
332

333
func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) {
3✔
334
        s.interceptor = interceptor
3✔
335

3✔
336
        // Replay all currently held htlcs. When an interceptor is not required,
3✔
337
        // there may be none because they've been cleared after the previous
3✔
338
        // disconnect.
3✔
339
        if interceptor != nil {
6✔
340
                log.Debugf("Interceptor connected")
3✔
341

3✔
342
                s.heldHtlcSet.forEach(s.sendForward)
3✔
343

3✔
344
                return
3✔
345
        }
3✔
346

347
        // The interceptor disconnects. If an interceptor is required, keep the
348
        // held htlcs.
349
        if s.requireInterceptor {
6✔
350
                log.Infof("Interceptor disconnected, retaining held packets")
3✔
351

3✔
352
                return
3✔
353
        }
3✔
354

355
        // Interceptor is not required. Release held forwards.
356
        log.Infof("Interceptor disconnected, resolving held packets")
3✔
357

3✔
358
        s.heldHtlcSet.popAll(func(fwd InterceptedForward) {
6✔
359
                err := fwd.Resume()
3✔
360
                if err != nil {
3✔
361
                        log.Errorf("Failed to resume hold forward %v", err)
×
362
                }
×
363
        })
364
}
365

366
func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
3✔
367
        intercepted, err := s.heldHtlcSet.pop(res.Key)
3✔
368
        if err != nil {
3✔
369
                return err
×
370
        }
×
371

372
        switch res.Action {
3✔
373
        case FwdActionResume:
3✔
374
                return intercepted.Resume()
3✔
375

376
        case FwdActionSettle:
3✔
377
                return intercepted.Settle(res.Preimage)
3✔
378

379
        case FwdActionFail:
3✔
380
                if len(res.FailureMessage) > 0 {
3✔
381
                        return intercepted.Fail(res.FailureMessage)
×
382
                }
×
383

384
                return intercepted.FailWithCode(res.FailureCode)
3✔
385

386
        default:
×
387
                return fmt.Errorf("unrecognized action %v", res.Action)
×
388
        }
389
}
390

391
// Resolve resolves an intercepted packet.
392
func (s *InterceptableSwitch) Resolve(res *FwdResolution) error {
3✔
393
        internalRes := &fwdResolution{
3✔
394
                resolution: res,
3✔
395
                errChan:    make(chan error, 1),
3✔
396
        }
3✔
397

3✔
398
        select {
3✔
399
        case s.resolutionChan <- internalRes:
3✔
400

401
        case <-s.quit:
×
402
                return errors.New("switch shutting down")
×
403
        }
404

405
        select {
3✔
406
        case err := <-internalRes.errChan:
3✔
407
                return err
3✔
408

409
        case <-s.quit:
×
410
                return errors.New("switch shutting down")
×
411
        }
412
}
413

414
// ForwardPackets attempts to forward the batch of htlcs to a connected
415
// interceptor. If the interceptor signals the resume action, the htlcs are
416
// forwarded to the switch. The link's quit signal should be provided to allow
417
// cancellation of forwarding during link shutdown.
418
func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, isReplay bool,
419
        packets ...*htlcPacket) error {
3✔
420

3✔
421
        // Synchronize with the main event loop. This should be light in the
3✔
422
        // case where there is no interceptor.
3✔
423
        select {
3✔
424
        case s.intercepted <- &interceptedPackets{
425
                packets:  packets,
426
                linkQuit: linkQuit,
427
                isReplay: isReplay,
428
        }:
3✔
429

430
        case <-linkQuit:
×
431
                log.Debugf("Forward cancelled because link quit")
×
432

433
        case <-s.quit:
×
434
                return errors.New("interceptable switch quit")
×
435
        }
436

437
        return nil
3✔
438
}
439

440
// ForwardPacket forwards a single htlc to the external interceptor.
441
func (s *InterceptableSwitch) ForwardPacket(
442
        fwd InterceptedForward) error {
3✔
443

3✔
444
        select {
3✔
445
        case s.onchainIntercepted <- fwd:
3✔
446

447
        case <-s.quit:
×
448
                return errors.New("interceptable switch quit")
×
449
        }
450

451
        return nil
3✔
452
}
453

454
// interceptForward forwards the packet to the external interceptor after
455
// checking the interception criteria.
456
func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
457
        isReplay bool) (bool, error) {
3✔
458

3✔
459
        switch htlc := packet.htlc.(type) {
3✔
460
        case *lnwire.UpdateAddHTLC:
3✔
461
                // We are not interested in intercepting initiated payments.
3✔
462
                if packet.incomingChanID == hop.Source {
3✔
463
                        return false, nil
×
464
                }
×
465

466
                intercepted := &interceptedForward{
3✔
467
                        htlc:       htlc,
3✔
468
                        packet:     packet,
3✔
469
                        htlcSwitch: s.htlcSwitch,
3✔
470
                        autoFailHeight: int32(packet.incomingTimeout -
3✔
471
                                s.cltvRejectDelta),
3✔
472
                }
3✔
473

3✔
474
                // Handle forwards that are too close to expiry.
3✔
475
                handled, err := s.handleExpired(intercepted)
3✔
476
                if err != nil {
3✔
477
                        log.Errorf("Error handling intercepted htlc "+
×
478
                                "that expires too soon: circuit=%v, "+
×
479
                                "incoming_timeout=%v, err=%v",
×
480
                                packet.inKey(), packet.incomingTimeout, err)
×
481

×
482
                        // Return false so that the packet is offered as normal
×
483
                        // to the switch. This isn't ideal because interception
×
484
                        // may be configured as always-on and is skipped now.
×
485
                        // Returning true isn't great either, because the htlc
×
486
                        // will remain stuck and potentially force-close the
×
487
                        // channel. But in the end, we should never get here, so
×
488
                        // the actual return value doesn't matter that much.
×
489
                        return false, nil
×
490
                }
×
491
                if handled {
3✔
492
                        return true, nil
×
493
                }
×
494

495
                return s.forward(intercepted, isReplay)
3✔
496

497
        default:
3✔
498
                return false, nil
3✔
499
        }
500
}
501

502
// forward records the intercepted htlc and forwards it to the interceptor.
503
func (s *InterceptableSwitch) forward(
504
        fwd InterceptedForward, isReplay bool) (bool, error) {
3✔
505

3✔
506
        inKey := fwd.Packet().IncomingCircuit
3✔
507

3✔
508
        // Ignore already held htlcs.
3✔
509
        if s.heldHtlcSet.exists(inKey) {
6✔
510
                return true, nil
3✔
511
        }
3✔
512

513
        // If there is no interceptor currently registered, configuration and packet
514
        // replay status determine how the packet is handled.
515
        if s.interceptor == nil {
6✔
516
                // Process normally if an interceptor is not required.
3✔
517
                if !s.requireInterceptor {
6✔
518
                        return false, nil
3✔
519
                }
3✔
520

521
                // We are in interceptor-required mode. If this is a new packet, it is
522
                // still safe to fail back. The interceptor has never seen this packet
523
                // yet. This limits the backlog of htlcs when the interceptor is down.
524
                if !isReplay {
×
525
                        err := fwd.FailWithCode(
×
526
                                lnwire.CodeTemporaryChannelFailure,
×
527
                        )
×
528
                        if err != nil {
×
529
                                log.Errorf("Cannot fail packet: %v", err)
×
530
                        }
×
531

532
                        return true, nil
×
533
                }
534

535
                // This packet is a replay. It is not safe to fail back, because the
536
                // interceptor may still signal otherwise upon reconnect. Keep the
537
                // packet in the queue until then.
538
                if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
×
539
                        return false, err
×
540
                }
×
541

542
                return true, nil
×
543
        }
544

545
        // There is an interceptor registered. We can forward the packet right now.
546
        // Hold it in the queue too to track what is outstanding.
547
        if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
3✔
548
                return false, err
×
549
        }
×
550

551
        s.sendForward(fwd)
3✔
552

3✔
553
        return true, nil
3✔
554
}
555

556
// handleExpired checks that the htlc isn't too close to the channel
557
// force-close broadcast height. If it is, it is cancelled back.
558
func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) (
559
        bool, error) {
3✔
560

3✔
561
        height := uint32(s.currentHeight)
3✔
562
        if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta {
6✔
563
                return false, nil
3✔
564
        }
3✔
565

566
        log.Debugf("Interception rejected because htlc "+
×
567
                "expires too soon: circuit=%v, "+
×
568
                "height=%v, incoming_timeout=%v",
×
569
                fwd.packet.inKey(), height,
×
570
                fwd.packet.incomingTimeout)
×
571

×
572
        err := fwd.FailWithCode(
×
573
                lnwire.CodeExpiryTooSoon,
×
574
        )
×
575
        if err != nil {
×
576
                return false, err
×
577
        }
×
578

579
        return true, nil
×
580
}
581

582
// interceptedForward implements the InterceptedForward interface.
583
// It is passed from the switch to external interceptors that are interested
584
// in holding forwards and resolve them manually.
585
type interceptedForward struct {
586
        htlc           *lnwire.UpdateAddHTLC
587
        packet         *htlcPacket
588
        htlcSwitch     *Switch
589
        autoFailHeight int32
590
}
591

592
// Packet returns the intercepted htlc packet.
593
func (f *interceptedForward) Packet() InterceptedPacket {
3✔
594
        return InterceptedPacket{
3✔
595
                IncomingCircuit: models.CircuitKey{
3✔
596
                        ChanID: f.packet.incomingChanID,
3✔
597
                        HtlcID: f.packet.incomingHTLCID,
3✔
598
                },
3✔
599
                OutgoingChanID: f.packet.outgoingChanID,
3✔
600
                Hash:           f.htlc.PaymentHash,
3✔
601
                OutgoingExpiry: f.htlc.Expiry,
3✔
602
                OutgoingAmount: f.htlc.Amount,
3✔
603
                IncomingAmount: f.packet.incomingAmount,
3✔
604
                IncomingExpiry: f.packet.incomingTimeout,
3✔
605
                CustomRecords:  f.packet.customRecords,
3✔
606
                OnionBlob:      f.htlc.OnionBlob,
3✔
607
                AutoFailHeight: f.autoFailHeight,
3✔
608
        }
3✔
609
}
3✔
610

611
// Resume resumes the default behavior as if the packet was not intercepted.
612
func (f *interceptedForward) Resume() error {
3✔
613
        // Forward to the switch. A link quit channel isn't needed, because we
3✔
614
        // are on a different thread now.
3✔
615
        return f.htlcSwitch.ForwardPackets(nil, f.packet)
3✔
616
}
3✔
617

618
// Fail notifies the intention to Fail an existing hold forward with an
619
// encrypted failure reason.
620
func (f *interceptedForward) Fail(reason []byte) error {
×
621
        obfuscatedReason := f.packet.obfuscator.IntermediateEncrypt(reason)
×
622

×
623
        return f.resolve(&lnwire.UpdateFailHTLC{
×
624
                Reason: obfuscatedReason,
×
625
        })
×
626
}
×
627

628
// FailWithCode notifies the intention to fail an existing hold forward with the
629
// specified failure code.
630
func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error {
3✔
631
        shaOnionBlob := func() [32]byte {
3✔
632
                return sha256.Sum256(f.htlc.OnionBlob[:])
×
633
        }
×
634

635
        // Create a local failure.
636
        var failureMsg lnwire.FailureMessage
3✔
637

3✔
638
        switch code {
3✔
639
        case lnwire.CodeInvalidOnionVersion:
×
640
                failureMsg = &lnwire.FailInvalidOnionVersion{
×
641
                        OnionSHA256: shaOnionBlob(),
×
642
                }
×
643

644
        case lnwire.CodeInvalidOnionHmac:
×
645
                failureMsg = &lnwire.FailInvalidOnionHmac{
×
646
                        OnionSHA256: shaOnionBlob(),
×
647
                }
×
648

649
        case lnwire.CodeInvalidOnionKey:
×
650
                failureMsg = &lnwire.FailInvalidOnionKey{
×
651
                        OnionSHA256: shaOnionBlob(),
×
652
                }
×
653

654
        case lnwire.CodeTemporaryChannelFailure:
3✔
655
                update := f.htlcSwitch.failAliasUpdate(
3✔
656
                        f.packet.incomingChanID, true,
3✔
657
                )
3✔
658
                if update == nil {
6✔
659
                        // Fallback to the original, non-alias behavior.
3✔
660
                        var err error
3✔
661
                        update, err = f.htlcSwitch.cfg.FetchLastChannelUpdate(
3✔
662
                                f.packet.incomingChanID,
3✔
663
                        )
3✔
664
                        if err != nil {
3✔
665
                                return err
×
666
                        }
×
667
                }
668

669
                failureMsg = lnwire.NewTemporaryChannelFailure(update)
3✔
670

671
        case lnwire.CodeExpiryTooSoon:
×
672
                update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate(
×
673
                        f.packet.incomingChanID,
×
674
                )
×
675
                if err != nil {
×
676
                        return err
×
677
                }
×
678

679
                failureMsg = lnwire.NewExpiryTooSoon(*update)
×
680

681
        default:
×
682
                return ErrUnsupportedFailureCode
×
683
        }
684

685
        // Encrypt the failure for the first hop. This node will be the origin
686
        // of the failure.
687
        reason, err := f.packet.obfuscator.EncryptFirstHop(failureMsg)
3✔
688
        if err != nil {
3✔
689
                return fmt.Errorf("failed to encrypt failure reason %w", err)
×
690
        }
×
691

692
        return f.resolve(&lnwire.UpdateFailHTLC{
3✔
693
                Reason: reason,
3✔
694
        })
3✔
695
}
696

697
// Settle forwards a settled packet to the switch.
698
func (f *interceptedForward) Settle(preimage lntypes.Preimage) error {
3✔
699
        if !preimage.Matches(f.htlc.PaymentHash) {
3✔
700
                return errors.New("preimage does not match hash")
×
701
        }
×
702
        return f.resolve(&lnwire.UpdateFulfillHTLC{
3✔
703
                PaymentPreimage: preimage,
3✔
704
        })
3✔
705
}
706

707
// resolve is used for both Settle and Fail and forwards the message to the
708
// switch.
709
func (f *interceptedForward) resolve(message lnwire.Message) error {
3✔
710
        pkt := &htlcPacket{
3✔
711
                incomingChanID: f.packet.incomingChanID,
3✔
712
                incomingHTLCID: f.packet.incomingHTLCID,
3✔
713
                outgoingChanID: f.packet.outgoingChanID,
3✔
714
                outgoingHTLCID: f.packet.outgoingHTLCID,
3✔
715
                isResolution:   true,
3✔
716
                circuit:        f.packet.circuit,
3✔
717
                htlc:           message,
3✔
718
                obfuscator:     f.packet.obfuscator,
3✔
719
                sourceRef:      f.packet.sourceRef,
3✔
720
        }
3✔
721
        return f.htlcSwitch.mailOrchestrator.Deliver(pkt.incomingChanID, pkt)
3✔
722
}
3✔
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