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

lightningnetwork / lnd / 10207481183

01 Aug 2024 11:52PM UTC coverage: 58.679% (+0.09%) from 58.591%
10207481183

push

github

web-flow
Merge pull request #8836 from hieblmi/payment-failure-reason-cancel

routing: add payment failure reason `FailureReasonCancel`

7 of 30 new or added lines in 5 files covered. (23.33%)

1662 existing lines in 21 files now uncovered.

125454 of 213798 relevant lines covered (58.68%)

28679.1 hits per line

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

84.18
/htlcswitch/interceptable_switch.go
1
package htlcswitch
2

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

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

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

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

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

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

40
        // htlcSwitch is the underline switch
41
        htlcSwitch *Switch
42

43
        // intercepted is where we stream all intercepted packets coming from
44
        // the switch.
45
        intercepted chan *interceptedPackets
46

47
        // resolutionChan is where we stream all responses coming from the
48
        // interceptor client.
49
        resolutionChan chan *fwdResolution
50

51
        onchainIntercepted chan InterceptedForward
52

53
        // interceptorRegistration is a channel that we use to synchronize
54
        // client connect and disconnect.
55
        interceptorRegistration chan ForwardInterceptor
56

57
        // requireInterceptor indicates whether processing should block if no
58
        // interceptor is connected.
59
        requireInterceptor bool
60

61
        // interceptor is the handler for intercepted packets.
62
        interceptor ForwardInterceptor
63

64
        // heldHtlcSet keeps track of outstanding intercepted forwards.
65
        heldHtlcSet *heldHtlcSet
66

67
        // cltvRejectDelta defines the number of blocks before the expiry of the
68
        // htlc where we no longer intercept it and instead cancel it back.
69
        cltvRejectDelta uint32
70

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

78
        // notifier is an instance of a chain notifier that we'll use to signal
79
        // the switch when a new block has arrived.
80
        notifier chainntnfs.ChainNotifier
81

82
        // blockEpochStream is an active block epoch event stream backed by an
83
        // active ChainNotifier instance. This will be used to retrieve the
84
        // latest height of the chain.
85
        blockEpochStream *chainntnfs.BlockEpochEvent
86

87
        // currentHeight is the currently best known height.
88
        currentHeight int32
89

90
        wg   sync.WaitGroup
91
        quit chan struct{}
92
}
93

94
type interceptedPackets struct {
95
        packets  []*htlcPacket
96
        linkQuit chan struct{}
97
        isReplay bool
98
}
99

100
// FwdAction defines the various resolution types.
101
type FwdAction int
102

103
const (
104
        // FwdActionResume forwards the intercepted packet to the switch.
105
        FwdActionResume FwdAction = iota
106

107
        // FwdActionSettle settles the intercepted packet with a preimage.
108
        FwdActionSettle
109

110
        // FwdActionFail fails the intercepted packet back to the sender.
111
        FwdActionFail
112
)
113

114
// FwdResolution defines the action to be taken on an intercepted packet.
115
type FwdResolution struct {
116
        // Key is the incoming circuit key of the htlc.
117
        Key models.CircuitKey
118

119
        // Action is the action to take on the intercepted htlc.
120
        Action FwdAction
121

122
        // Preimage is the preimage that is to be used for settling if Action is
123
        // FwdActionSettle.
124
        Preimage lntypes.Preimage
125

126
        // FailureMessage is the encrypted failure message that is to be passed
127
        // back to the sender if action is FwdActionFail.
128
        FailureMessage []byte
129

130
        // FailureCode is the failure code that is to be passed back to the
131
        // sender if action is FwdActionFail.
132
        FailureCode lnwire.FailCode
133
}
134

135
type fwdResolution struct {
136
        resolution *FwdResolution
137
        errChan    chan error
138
}
139

140
// InterceptableSwitchConfig contains the configuration of InterceptableSwitch.
141
type InterceptableSwitchConfig struct {
142
        // Switch is a reference to the actual switch implementation that
143
        // packets get sent to on resume.
144
        Switch *Switch
145

146
        // Notifier is an instance of a chain notifier that we'll use to signal
147
        // the switch when a new block has arrived.
148
        Notifier chainntnfs.ChainNotifier
149

150
        // CltvRejectDelta defines the number of blocks before the expiry of the
151
        // htlc where we auto-fail an intercepted htlc to prevent channel
152
        // force-closure.
153
        CltvRejectDelta uint32
154

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

162
        // RequireInterceptor indicates whether processing should block if no
163
        // interceptor is connected.
164
        RequireInterceptor bool
165
}
166

167
// NewInterceptableSwitch returns an instance of InterceptableSwitch.
168
func NewInterceptableSwitch(cfg *InterceptableSwitchConfig) (
169
        *InterceptableSwitch, error) {
27✔
170

27✔
171
        if cfg.CltvInterceptDelta <= cfg.CltvRejectDelta {
27✔
UNCOV
172
                return nil, fmt.Errorf("cltv intercept delta %v not greater "+
×
UNCOV
173
                        "than cltv reject delta %v",
×
UNCOV
174
                        cfg.CltvInterceptDelta, cfg.CltvRejectDelta)
×
UNCOV
175
        }
×
176

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

27✔
189
                quit: make(chan struct{}),
27✔
190
        }, nil
27✔
191
}
192

193
// SetInterceptor sets the ForwardInterceptor to be used. A nil argument
194
// unregisters the current interceptor.
195
func (s *InterceptableSwitch) SetInterceptor(
196
        interceptor ForwardInterceptor) {
13✔
197

13✔
198
        // Synchronize setting the handler with the main loop to prevent race
13✔
199
        // conditions.
13✔
200
        select {
13✔
201
        case s.interceptorRegistration <- interceptor:
13✔
202

UNCOV
203
        case <-s.quit:
×
204
        }
205
}
206

207
func (s *InterceptableSwitch) Start() error {
9✔
208
        log.Info("InterceptableSwitch starting...")
9✔
209

9✔
210
        if s.started.Swap(true) {
9✔
UNCOV
211
                return fmt.Errorf("InterceptableSwitch started more than once")
×
UNCOV
212
        }
×
213

214
        blockEpochStream, err := s.notifier.RegisterBlockEpochNtfn(nil)
9✔
215
        if err != nil {
9✔
UNCOV
216
                return err
×
UNCOV
217
        }
×
218
        s.blockEpochStream = blockEpochStream
9✔
219

9✔
220
        s.wg.Add(1)
9✔
221
        go func() {
18✔
222
                defer s.wg.Done()
9✔
223

9✔
224
                err := s.run()
9✔
225
                if err != nil {
13✔
226
                        log.Errorf("InterceptableSwitch stopped: %v", err)
4✔
227
                }
4✔
228
        }()
229

230
        log.Debug("InterceptableSwitch started")
9✔
231

9✔
232
        return nil
9✔
233
}
234

235
func (s *InterceptableSwitch) Stop() error {
4✔
236
        log.Info("InterceptableSwitch shutting down...")
4✔
237

4✔
238
        if s.stopped.Swap(true) {
4✔
239
                return fmt.Errorf("InterceptableSwitch stopped more than once")
×
UNCOV
240
        }
×
241

242
        close(s.quit)
4✔
243
        s.wg.Wait()
4✔
244

4✔
245
        // We need to check whether the start routine run and initialized the
4✔
246
        // `blockEpochStream`.
4✔
247
        if s.blockEpochStream != nil {
8✔
248
                s.blockEpochStream.Cancel()
4✔
249
        }
4✔
250

251
        log.Debug("InterceptableSwitch shutdown complete")
4✔
252

4✔
253
        return nil
4✔
254
}
255

256
func (s *InterceptableSwitch) run() error {
9✔
257
        // The block epoch stream will immediately stream the current height.
9✔
258
        // Read it out here.
9✔
259
        select {
9✔
260
        case currentBlock, ok := <-s.blockEpochStream.Epochs:
9✔
261
                if !ok {
9✔
262
                        return errBlockStreamStopped
×
263
                }
×
264
                s.currentHeight = currentBlock.Height
9✔
265

UNCOV
266
        case <-s.quit:
×
UNCOV
267
                return nil
×
268
        }
269

270
        log.Debugf("InterceptableSwitch running: height=%v, "+
9✔
271
                "requireInterceptor=%v", s.currentHeight, s.requireInterceptor)
9✔
272

9✔
273
        for {
51✔
274
                select {
42✔
275
                // An interceptor registration or de-registration came in.
276
                case interceptor := <-s.interceptorRegistration:
13✔
277
                        s.setInterceptor(interceptor)
13✔
278

279
                case packets := <-s.intercepted:
20✔
280
                        var notIntercepted []*htlcPacket
20✔
281
                        for _, p := range packets.packets {
40✔
282
                                intercepted, err := s.interceptForward(
20✔
283
                                        p, packets.isReplay,
20✔
284
                                )
20✔
285
                                if err != nil {
20✔
UNCOV
286
                                        return err
×
287
                                }
×
288

289
                                if !intercepted {
28✔
290
                                        notIntercepted = append(
8✔
291
                                                notIntercepted, p,
8✔
292
                                        )
8✔
293
                                }
8✔
294
                        }
295
                        err := s.htlcSwitch.ForwardPackets(
20✔
296
                                packets.linkQuit, notIntercepted...,
20✔
297
                        )
20✔
298
                        if err != nil {
20✔
UNCOV
299
                                log.Errorf("Cannot forward packets: %v", err)
×
UNCOV
300
                        }
×
301

302
                case fwd := <-s.onchainIntercepted:
4✔
303
                        // For on-chain interceptions, we don't know if it has
4✔
304
                        // already been offered before. This information is in
4✔
305
                        // the forwarding package which isn't easily accessible
4✔
306
                        // from contractcourt. It is likely though that it was
4✔
307
                        // already intercepted in the off-chain flow. And even
4✔
308
                        // if not, it is safe to signal replay so that we won't
4✔
309
                        // unexpectedly skip over this htlc.
4✔
310
                        if _, err := s.forward(fwd, true); err != nil {
4✔
UNCOV
311
                                return err
×
UNCOV
312
                        }
×
313

314
                case res := <-s.resolutionChan:
11✔
315
                        res.errChan <- s.resolve(res.resolution)
11✔
316

317
                case currentBlock, ok := <-s.blockEpochStream.Epochs:
5✔
318
                        if !ok {
9✔
319
                                return errBlockStreamStopped
4✔
320
                        }
4✔
321

322
                        s.currentHeight = currentBlock.Height
5✔
323

5✔
324
                        // A new block is appended. Fail any held htlcs that
5✔
325
                        // expire at this height to prevent channel force-close.
5✔
326
                        s.failExpiredHtlcs()
5✔
327

328
                case <-s.quit:
4✔
329
                        return nil
4✔
330
                }
331
        }
332
}
333

334
func (s *InterceptableSwitch) failExpiredHtlcs() {
5✔
335
        s.heldHtlcSet.popAutoFails(
5✔
336
                uint32(s.currentHeight),
5✔
337
                func(fwd InterceptedForward) {
6✔
338
                        err := fwd.FailWithCode(
1✔
339
                                lnwire.CodeTemporaryChannelFailure,
1✔
340
                        )
1✔
341
                        if err != nil {
1✔
UNCOV
342
                                log.Errorf("Cannot fail packet: %v", err)
×
UNCOV
343
                        }
×
344
                },
345
        )
346
}
347

348
func (s *InterceptableSwitch) sendForward(fwd InterceptedForward) {
15✔
349
        err := s.interceptor(fwd.Packet())
15✔
350
        if err != nil {
15✔
UNCOV
351
                // Only log the error. If we couldn't send the packet, we assume
×
UNCOV
352
                // that the interceptor will reconnect so that we can retry.
×
UNCOV
353
                log.Debugf("Interceptor cannot handle forward: %v", err)
×
UNCOV
354
        }
×
355
}
356

357
func (s *InterceptableSwitch) setInterceptor(interceptor ForwardInterceptor) {
13✔
358
        s.interceptor = interceptor
13✔
359

13✔
360
        // Replay all currently held htlcs. When an interceptor is not required,
13✔
361
        // there may be none because they've been cleared after the previous
13✔
362
        // disconnect.
13✔
363
        if interceptor != nil {
24✔
364
                log.Debugf("Interceptor connected")
11✔
365

11✔
366
                s.heldHtlcSet.forEach(s.sendForward)
11✔
367

11✔
368
                return
11✔
369
        }
11✔
370

371
        // The interceptor disconnects. If an interceptor is required, keep the
372
        // held htlcs.
373
        if s.requireInterceptor {
11✔
374
                log.Infof("Interceptor disconnected, retaining held packets")
5✔
375

5✔
376
                return
5✔
377
        }
5✔
378

379
        // Interceptor is not required. Release held forwards.
380
        log.Infof("Interceptor disconnected, resolving held packets")
5✔
381

5✔
382
        s.heldHtlcSet.popAll(func(fwd InterceptedForward) {
10✔
383
                err := fwd.Resume()
5✔
384
                if err != nil {
5✔
UNCOV
385
                        log.Errorf("Failed to resume hold forward %v", err)
×
386
                }
×
387
        })
388
}
389

390
func (s *InterceptableSwitch) resolve(res *FwdResolution) error {
13✔
391
        intercepted, err := s.heldHtlcSet.pop(res.Key)
13✔
392
        if err != nil {
14✔
393
                return err
1✔
394
        }
1✔
395

396
        switch res.Action {
12✔
397
        case FwdActionResume:
5✔
398
                return intercepted.Resume()
5✔
399

400
        case FwdActionSettle:
6✔
401
                return intercepted.Settle(res.Preimage)
6✔
402

403
        case FwdActionFail:
9✔
404
                if len(res.FailureMessage) > 0 {
10✔
405
                        return intercepted.Fail(res.FailureMessage)
1✔
406
                }
1✔
407

408
                return intercepted.FailWithCode(res.FailureCode)
8✔
409

410
        default:
×
UNCOV
411
                return fmt.Errorf("unrecognized action %v", res.Action)
×
412
        }
413
}
414

415
// Resolve resolves an intercepted packet.
416
func (s *InterceptableSwitch) Resolve(res *FwdResolution) error {
11✔
417
        internalRes := &fwdResolution{
11✔
418
                resolution: res,
11✔
419
                errChan:    make(chan error, 1),
11✔
420
        }
11✔
421

11✔
422
        select {
11✔
423
        case s.resolutionChan <- internalRes:
11✔
424

UNCOV
425
        case <-s.quit:
×
UNCOV
426
                return errors.New("switch shutting down")
×
427
        }
428

429
        select {
11✔
430
        case err := <-internalRes.errChan:
11✔
431
                return err
11✔
432

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

438
// ForwardPackets attempts to forward the batch of htlcs to a connected
439
// interceptor. If the interceptor signals the resume action, the htlcs are
440
// forwarded to the switch. The link's quit signal should be provided to allow
441
// cancellation of forwarding during link shutdown.
442
func (s *InterceptableSwitch) ForwardPackets(linkQuit chan struct{}, isReplay bool,
443
        packets ...*htlcPacket) error {
20✔
444

20✔
445
        // Synchronize with the main event loop. This should be light in the
20✔
446
        // case where there is no interceptor.
20✔
447
        select {
20✔
448
        case s.intercepted <- &interceptedPackets{
449
                packets:  packets,
450
                linkQuit: linkQuit,
451
                isReplay: isReplay,
452
        }:
20✔
453

UNCOV
454
        case <-linkQuit:
×
UNCOV
455
                log.Debugf("Forward cancelled because link quit")
×
456

UNCOV
457
        case <-s.quit:
×
UNCOV
458
                return errors.New("interceptable switch quit")
×
459
        }
460

461
        return nil
20✔
462
}
463

464
// ForwardPacket forwards a single htlc to the external interceptor.
465
func (s *InterceptableSwitch) ForwardPacket(
466
        fwd InterceptedForward) error {
4✔
467

4✔
468
        select {
4✔
469
        case s.onchainIntercepted <- fwd:
4✔
470

UNCOV
471
        case <-s.quit:
×
UNCOV
472
                return errors.New("interceptable switch quit")
×
473
        }
474

475
        return nil
4✔
476
}
477

478
// interceptForward forwards the packet to the external interceptor after
479
// checking the interception criteria.
480
func (s *InterceptableSwitch) interceptForward(packet *htlcPacket,
481
        isReplay bool) (bool, error) {
20✔
482

20✔
483
        switch htlc := packet.htlc.(type) {
20✔
484
        case *lnwire.UpdateAddHTLC:
17✔
485
                // We are not interested in intercepting initiated payments.
17✔
486
                if packet.incomingChanID == hop.Source {
17✔
UNCOV
487
                        return false, nil
×
UNCOV
488
                }
×
489

490
                intercepted := &interceptedForward{
17✔
491
                        htlc:       htlc,
17✔
492
                        packet:     packet,
17✔
493
                        htlcSwitch: s.htlcSwitch,
17✔
494
                        autoFailHeight: int32(packet.incomingTimeout -
17✔
495
                                s.cltvRejectDelta),
17✔
496
                }
17✔
497

17✔
498
                // Handle forwards that are too close to expiry.
17✔
499
                handled, err := s.handleExpired(intercepted)
17✔
500
                if err != nil {
18✔
501
                        log.Errorf("Error handling intercepted htlc "+
1✔
502
                                "that expires too soon: circuit=%v, "+
1✔
503
                                "incoming_timeout=%v, err=%v",
1✔
504
                                packet.inKey(), packet.incomingTimeout, err)
1✔
505

1✔
506
                        // Return false so that the packet is offered as normal
1✔
507
                        // to the switch. This isn't ideal because interception
1✔
508
                        // may be configured as always-on and is skipped now.
1✔
509
                        // Returning true isn't great either, because the htlc
1✔
510
                        // will remain stuck and potentially force-close the
1✔
511
                        // channel. But in the end, we should never get here, so
1✔
512
                        // the actual return value doesn't matter that much.
1✔
513
                        return false, nil
1✔
514
                }
1✔
515
                if handled {
17✔
516
                        return true, nil
1✔
517
                }
1✔
518

519
                return s.forward(intercepted, isReplay)
15✔
520

521
        default:
7✔
522
                return false, nil
7✔
523
        }
524
}
525

526
// forward records the intercepted htlc and forwards it to the interceptor.
527
func (s *InterceptableSwitch) forward(
528
        fwd InterceptedForward, isReplay bool) (bool, error) {
15✔
529

15✔
530
        inKey := fwd.Packet().IncomingCircuit
15✔
531

15✔
532
        // Ignore already held htlcs.
15✔
533
        if s.heldHtlcSet.exists(inKey) {
19✔
534
                return true, nil
4✔
535
        }
4✔
536

537
        // If there is no interceptor currently registered, configuration and packet
538
        // replay status determine how the packet is handled.
539
        if s.interceptor == nil {
21✔
540
                // Process normally if an interceptor is not required.
6✔
541
                if !s.requireInterceptor {
10✔
542
                        return false, nil
4✔
543
                }
4✔
544

545
                // We are in interceptor-required mode. If this is a new packet, it is
546
                // still safe to fail back. The interceptor has never seen this packet
547
                // yet. This limits the backlog of htlcs when the interceptor is down.
548
                if !isReplay {
3✔
549
                        err := fwd.FailWithCode(
1✔
550
                                lnwire.CodeTemporaryChannelFailure,
1✔
551
                        )
1✔
552
                        if err != nil {
1✔
UNCOV
553
                                log.Errorf("Cannot fail packet: %v", err)
×
UNCOV
554
                        }
×
555

556
                        return true, nil
1✔
557
                }
558

559
                // This packet is a replay. It is not safe to fail back, because the
560
                // interceptor may still signal otherwise upon reconnect. Keep the
561
                // packet in the queue until then.
562
                if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
1✔
UNCOV
563
                        return false, err
×
UNCOV
564
                }
×
565

566
                return true, nil
1✔
567
        }
568

569
        // There is an interceptor registered. We can forward the packet right now.
570
        // Hold it in the queue too to track what is outstanding.
571
        if err := s.heldHtlcSet.push(inKey, fwd); err != nil {
13✔
UNCOV
572
                return false, err
×
UNCOV
573
        }
×
574

575
        s.sendForward(fwd)
13✔
576

13✔
577
        return true, nil
13✔
578
}
579

580
// handleExpired checks that the htlc isn't too close to the channel
581
// force-close broadcast height. If it is, it is cancelled back.
582
func (s *InterceptableSwitch) handleExpired(fwd *interceptedForward) (
583
        bool, error) {
17✔
584

17✔
585
        height := uint32(s.currentHeight)
17✔
586
        if fwd.packet.incomingTimeout >= height+s.cltvInterceptDelta {
32✔
587
                return false, nil
15✔
588
        }
15✔
589

590
        log.Debugf("Interception rejected because htlc "+
2✔
591
                "expires too soon: circuit=%v, "+
2✔
592
                "height=%v, incoming_timeout=%v",
2✔
593
                fwd.packet.inKey(), height,
2✔
594
                fwd.packet.incomingTimeout)
2✔
595

2✔
596
        err := fwd.FailWithCode(
2✔
597
                lnwire.CodeExpiryTooSoon,
2✔
598
        )
2✔
599
        if err != nil {
3✔
600
                return false, err
1✔
601
        }
1✔
602

603
        return true, nil
1✔
604
}
605

606
// interceptedForward implements the InterceptedForward interface.
607
// It is passed from the switch to external interceptors that are interested
608
// in holding forwards and resolve them manually.
609
type interceptedForward struct {
610
        htlc           *lnwire.UpdateAddHTLC
611
        packet         *htlcPacket
612
        htlcSwitch     *Switch
613
        autoFailHeight int32
614
}
615

616
// Packet returns the intercepted htlc packet.
617
func (f *interceptedForward) Packet() InterceptedPacket {
29✔
618
        return InterceptedPacket{
29✔
619
                IncomingCircuit: models.CircuitKey{
29✔
620
                        ChanID: f.packet.incomingChanID,
29✔
621
                        HtlcID: f.packet.incomingHTLCID,
29✔
622
                },
29✔
623
                OutgoingChanID: f.packet.outgoingChanID,
29✔
624
                Hash:           f.htlc.PaymentHash,
29✔
625
                OutgoingExpiry: f.htlc.Expiry,
29✔
626
                OutgoingAmount: f.htlc.Amount,
29✔
627
                IncomingAmount: f.packet.incomingAmount,
29✔
628
                IncomingExpiry: f.packet.incomingTimeout,
29✔
629
                CustomRecords:  f.packet.customRecords,
29✔
630
                OnionBlob:      f.htlc.OnionBlob,
29✔
631
                AutoFailHeight: f.autoFailHeight,
29✔
632
        }
29✔
633
}
29✔
634

635
// Resume resumes the default behavior as if the packet was not intercepted.
636
func (f *interceptedForward) Resume() error {
6✔
637
        // Forward to the switch. A link quit channel isn't needed, because we
6✔
638
        // are on a different thread now.
6✔
639
        return f.htlcSwitch.ForwardPackets(nil, f.packet)
6✔
640
}
6✔
641

642
// Fail notifies the intention to Fail an existing hold forward with an
643
// encrypted failure reason.
644
func (f *interceptedForward) Fail(reason []byte) error {
1✔
645
        obfuscatedReason := f.packet.obfuscator.IntermediateEncrypt(reason)
1✔
646

1✔
647
        return f.resolve(&lnwire.UpdateFailHTLC{
1✔
648
                Reason: obfuscatedReason,
1✔
649
        })
1✔
650
}
1✔
651

652
// FailWithCode notifies the intention to fail an existing hold forward with the
653
// specified failure code.
654
func (f *interceptedForward) FailWithCode(code lnwire.FailCode) error {
12✔
655
        shaOnionBlob := func() [32]byte {
13✔
656
                return sha256.Sum256(f.htlc.OnionBlob[:])
1✔
657
        }
1✔
658

659
        // Create a local failure.
660
        var failureMsg lnwire.FailureMessage
12✔
661

12✔
662
        switch code {
12✔
UNCOV
663
        case lnwire.CodeInvalidOnionVersion:
×
UNCOV
664
                failureMsg = &lnwire.FailInvalidOnionVersion{
×
665
                        OnionSHA256: shaOnionBlob(),
×
666
                }
×
667

UNCOV
668
        case lnwire.CodeInvalidOnionHmac:
×
UNCOV
669
                failureMsg = &lnwire.FailInvalidOnionHmac{
×
UNCOV
670
                        OnionSHA256: shaOnionBlob(),
×
UNCOV
671
                }
×
672

673
        case lnwire.CodeInvalidOnionKey:
1✔
674
                failureMsg = &lnwire.FailInvalidOnionKey{
1✔
675
                        OnionSHA256: shaOnionBlob(),
1✔
676
                }
1✔
677

678
        case lnwire.CodeTemporaryChannelFailure:
9✔
679
                update := f.htlcSwitch.failAliasUpdate(
9✔
680
                        f.packet.incomingChanID, true,
9✔
681
                )
9✔
682
                if update == nil {
16✔
683
                        // Fallback to the original, non-alias behavior.
7✔
684
                        var err error
7✔
685
                        update, err = f.htlcSwitch.cfg.FetchLastChannelUpdate(
7✔
686
                                f.packet.incomingChanID,
7✔
687
                        )
7✔
688
                        if err != nil {
7✔
689
                                return err
×
690
                        }
×
691
                }
692

693
                failureMsg = lnwire.NewTemporaryChannelFailure(update)
9✔
694

695
        case lnwire.CodeExpiryTooSoon:
2✔
696
                update, err := f.htlcSwitch.cfg.FetchLastChannelUpdate(
2✔
697
                        f.packet.incomingChanID,
2✔
698
                )
2✔
699
                if err != nil {
3✔
700
                        return err
1✔
701
                }
1✔
702

703
                failureMsg = lnwire.NewExpiryTooSoon(*update)
1✔
704

UNCOV
705
        default:
×
UNCOV
706
                return ErrUnsupportedFailureCode
×
707
        }
708

709
        // Encrypt the failure for the first hop. This node will be the origin
710
        // of the failure.
711
        reason, err := f.packet.obfuscator.EncryptFirstHop(failureMsg)
11✔
712
        if err != nil {
11✔
UNCOV
713
                return fmt.Errorf("failed to encrypt failure reason %w", err)
×
UNCOV
714
        }
×
715

716
        return f.resolve(&lnwire.UpdateFailHTLC{
11✔
717
                Reason: reason,
11✔
718
        })
11✔
719
}
720

721
// Settle forwards a settled packet to the switch.
722
func (f *interceptedForward) Settle(preimage lntypes.Preimage) error {
6✔
723
        if !preimage.Matches(f.htlc.PaymentHash) {
6✔
UNCOV
724
                return errors.New("preimage does not match hash")
×
UNCOV
725
        }
×
726
        return f.resolve(&lnwire.UpdateFulfillHTLC{
6✔
727
                PaymentPreimage: preimage,
6✔
728
        })
6✔
729
}
730

731
// resolve is used for both Settle and Fail and forwards the message to the
732
// switch.
733
func (f *interceptedForward) resolve(message lnwire.Message) error {
14✔
734
        pkt := &htlcPacket{
14✔
735
                incomingChanID: f.packet.incomingChanID,
14✔
736
                incomingHTLCID: f.packet.incomingHTLCID,
14✔
737
                outgoingChanID: f.packet.outgoingChanID,
14✔
738
                outgoingHTLCID: f.packet.outgoingHTLCID,
14✔
739
                isResolution:   true,
14✔
740
                circuit:        f.packet.circuit,
14✔
741
                htlc:           message,
14✔
742
                obfuscator:     f.packet.obfuscator,
14✔
743
                sourceRef:      f.packet.sourceRef,
14✔
744
        }
14✔
745
        return f.htlcSwitch.mailOrchestrator.Deliver(pkt.incomingChanID, pkt)
14✔
746
}
14✔
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