• 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

63.42
/routing/result_interpretation.go
1
package routing
2

3
import (
4
        "fmt"
5

6
        "github.com/lightningnetwork/lnd/channeldb"
7
        "github.com/lightningnetwork/lnd/lnwire"
8
        "github.com/lightningnetwork/lnd/routing/route"
9
)
10

11
// Instantiate variables to allow taking a reference from the failure reason.
12
var (
13
        reasonError            = channeldb.FailureReasonError
14
        reasonIncorrectDetails = channeldb.FailureReasonPaymentDetails
15
)
16

17
// pairResult contains the result of the interpretation of a payment attempt for
18
// a specific node pair.
19
type pairResult struct {
20
        // amt is the amount that was forwarded for this pair. Can be set to
21
        // zero for failures that are amount independent.
22
        amt lnwire.MilliSatoshi
23

24
        // success indicates whether the payment attempt was successful through
25
        // this pair.
26
        success bool
27
}
28

29
// failPairResult creates a new result struct for a failure.
30
func failPairResult(minPenalizeAmt lnwire.MilliSatoshi) pairResult {
3✔
31
        return pairResult{
3✔
32
                amt: minPenalizeAmt,
3✔
33
        }
3✔
34
}
3✔
35

36
// newSuccessPairResult creates a new result struct for a success.
37
func successPairResult(successAmt lnwire.MilliSatoshi) pairResult {
3✔
38
        return pairResult{
3✔
39
                success: true,
3✔
40
                amt:     successAmt,
3✔
41
        }
3✔
42
}
3✔
43

44
// String returns the human-readable representation of a pair result.
45
func (p pairResult) String() string {
×
46
        var resultType string
×
47
        if p.success {
×
48
                resultType = "success"
×
49
        } else {
×
50
                resultType = "failed"
×
51
        }
×
52

53
        return fmt.Sprintf("%v (amt=%v)", resultType, p.amt)
×
54
}
55

56
// interpretedResult contains the result of the interpretation of a payment
57
// attempt.
58
type interpretedResult struct {
59
        // nodeFailure points to a node pubkey if all channels of that node are
60
        // responsible for the result.
61
        nodeFailure *route.Vertex
62

63
        // pairResults contains a map of node pairs for which we have a result.
64
        pairResults map[DirectedNodePair]pairResult
65

66
        // finalFailureReason is set to a non-nil value if it makes no more
67
        // sense to start another payment attempt. It will contain the reason
68
        // why.
69
        finalFailureReason *channeldb.FailureReason
70

71
        // policyFailure is set to a node pair if there is a policy failure on
72
        // that connection. This is used to control the second chance logic for
73
        // policy failures.
74
        policyFailure *DirectedNodePair
75
}
76

77
// interpretResult interprets a payment outcome and returns an object that
78
// contains information required to update mission control.
79
func interpretResult(rt *route.Route, success bool, failureSrcIdx *int,
80
        failure lnwire.FailureMessage) *interpretedResult {
3✔
81

3✔
82
        i := &interpretedResult{
3✔
83
                pairResults: make(map[DirectedNodePair]pairResult),
3✔
84
        }
3✔
85

3✔
86
        if success {
6✔
87
                i.processSuccess(rt)
3✔
88
        } else {
6✔
89
                i.processFail(rt, failureSrcIdx, failure)
3✔
90
        }
3✔
91
        return i
3✔
92
}
93

94
// processSuccess processes a successful payment attempt.
95
func (i *interpretedResult) processSuccess(route *route.Route) {
3✔
96
        // For successes, all nodes must have acted in the right way. Therefore
3✔
97
        // we mark all of them with a success result.
3✔
98
        i.successPairRange(route, 0, len(route.Hops)-1)
3✔
99
}
3✔
100

101
// processFail processes a failed payment attempt.
102
func (i *interpretedResult) processFail(
103
        rt *route.Route, errSourceIdx *int,
104
        failure lnwire.FailureMessage) {
3✔
105

3✔
106
        if errSourceIdx == nil {
3✔
107
                i.processPaymentOutcomeUnknown(rt)
×
108
                return
×
109
        }
×
110

111
        // If the payment was to a blinded route and we received an error from
112
        // after the introduction point, handle this error separately - there
113
        // has been a protocol violation from the introduction node. This
114
        // penalty applies regardless of the error code that is returned.
115
        introIdx, isBlinded := introductionPointIndex(rt)
3✔
116
        if isBlinded && introIdx < *errSourceIdx {
3✔
117
                i.processPaymentOutcomeBadIntro(rt, introIdx, *errSourceIdx)
×
118
                return
×
119
        }
×
120

121
        switch *errSourceIdx {
3✔
122

123
        // We are the source of the failure.
124
        case 0:
3✔
125
                i.processPaymentOutcomeSelf(rt, failure)
3✔
126

127
        // A failure from the final hop was received.
128
        case len(rt.Hops):
3✔
129
                i.processPaymentOutcomeFinal(
3✔
130
                        rt, failure,
3✔
131
                )
3✔
132

133
        // An intermediate hop failed. Interpret the outcome, update reputation
134
        // and try again.
135
        default:
3✔
136
                i.processPaymentOutcomeIntermediate(
3✔
137
                        rt, *errSourceIdx, failure,
3✔
138
                )
3✔
139
        }
140
}
141

142
// processPaymentOutcomeBadIntro handles the case where we have made payment
143
// to a blinded route, but received an error from a node after the introduction
144
// node. This indicates that the introduction node is not obeying the route
145
// blinding specification, as we expect all errors from the introduction node
146
// to be source from it.
147
func (i *interpretedResult) processPaymentOutcomeBadIntro(route *route.Route,
148
        introIdx, errSourceIdx int) {
×
149

×
150
        // We fail the introduction node for not obeying the specification.
×
151
        i.failNode(route, introIdx)
×
152

×
153
        // Other preceding channels in the route forwarded correctly. Note
×
154
        // that we do not assign success to the incoming link to the
×
155
        // introduction node because it has not handled the error correctly.
×
156
        if introIdx > 1 {
×
157
                i.successPairRange(route, 0, introIdx-2)
×
158
        }
×
159

160
        // If the source of the failure was from the final node, we also set
161
        // a final failure reason because the recipient can't process the
162
        // payment (independent of the introduction failing to convert the
163
        // error, we can't complete the payment if the last hop fails).
164
        if errSourceIdx == len(route.Hops) {
×
165
                i.finalFailureReason = &reasonError
×
166
        }
×
167
}
168

169
// processPaymentOutcomeSelf handles failures sent by ourselves.
170
func (i *interpretedResult) processPaymentOutcomeSelf(
171
        rt *route.Route, failure lnwire.FailureMessage) {
3✔
172

3✔
173
        switch failure.(type) {
3✔
174

175
        // We receive a malformed htlc failure from our peer. We trust ourselves
176
        // to send the correct htlc, so our peer must be at fault.
177
        case *lnwire.FailInvalidOnionVersion,
178
                *lnwire.FailInvalidOnionHmac,
179
                *lnwire.FailInvalidOnionKey:
×
180

×
181
                i.failNode(rt, 1)
×
182

×
183
                // If this was a payment to a direct peer, we can stop trying.
×
184
                if len(rt.Hops) == 1 {
×
185
                        i.finalFailureReason = &reasonError
×
186
                }
×
187

188
        // Any other failure originating from ourselves should be temporary and
189
        // caused by changing conditions between path finding and execution of
190
        // the payment. We just retry and trust that the information locally
191
        // available in the link has been updated.
192
        default:
3✔
193
                log.Warnf("Routing failure for local channel %v occurred",
3✔
194
                        rt.Hops[0].ChannelID)
3✔
195
        }
196
}
197

198
// processPaymentOutcomeFinal handles failures sent by the final hop.
199
func (i *interpretedResult) processPaymentOutcomeFinal(
200
        route *route.Route, failure lnwire.FailureMessage) {
3✔
201

3✔
202
        n := len(route.Hops)
3✔
203

3✔
204
        failNode := func() {
3✔
205
                i.failNode(route, n)
×
206

×
207
                // Other channels in the route forwarded correctly.
×
208
                if n > 1 {
×
209
                        i.successPairRange(route, 0, n-2)
×
210
                }
×
211

212
                i.finalFailureReason = &reasonError
×
213
        }
214

215
        // If a failure from the final node is received, we will fail the
216
        // payment in almost all cases. Only when the penultimate node sends an
217
        // incorrect htlc, we want to retry via another route. Invalid onion
218
        // failures are not expected, because the final node wouldn't be able to
219
        // encrypt that failure.
220
        switch failure.(type) {
3✔
221

222
        // Expiry or amount of the HTLC doesn't match the onion, try another
223
        // route.
224
        case *lnwire.FailFinalIncorrectCltvExpiry,
225
                *lnwire.FailFinalIncorrectHtlcAmount:
×
226

×
227
                // We trust ourselves. If this is a direct payment, we penalize
×
228
                // the final node and fail the payment.
×
229
                if n == 1 {
×
230
                        i.failNode(route, n)
×
231
                        i.finalFailureReason = &reasonError
×
232

×
233
                        return
×
234
                }
×
235

236
                // Otherwise penalize the last pair of the route and retry.
237
                // Either the final node is at fault, or it gets sent a bad htlc
238
                // from its predecessor.
239
                i.failPair(route, n-1)
×
240

×
241
                // The other hops relayed correctly, so assign those pairs a
×
242
                // success result. At this point, n >= 2.
×
243
                i.successPairRange(route, 0, n-2)
×
244

245
        // We are using wrong payment hash or amount, fail the payment.
246
        case *lnwire.FailIncorrectPaymentAmount,
247
                *lnwire.FailIncorrectDetails:
3✔
248

3✔
249
                // Assign all pairs a success result, as the payment reached the
3✔
250
                // destination correctly.
3✔
251
                i.successPairRange(route, 0, n-1)
3✔
252

3✔
253
                i.finalFailureReason = &reasonIncorrectDetails
3✔
254

255
        // The HTLC that was extended to the final hop expires too soon. Fail
256
        // the payment, because we may be using the wrong final cltv delta.
257
        case *lnwire.FailFinalExpiryTooSoon:
×
258
                // TODO(roasbeef): can happen to to race condition, try again
×
259
                // with recent block height
×
260

×
261
                // TODO(joostjager): can also happen because a node delayed
×
262
                // deliberately. What to penalize?
×
263
                i.finalFailureReason = &reasonIncorrectDetails
×
264

265
        case *lnwire.FailMPPTimeout:
×
266
                // Assign all pairs a success result, as the payment reached the
×
267
                // destination correctly. Continue the payment process.
×
268
                i.successPairRange(route, 0, n-1)
×
269

270
        // We do not expect to receive an invalid blinding error from the final
271
        // node in the route. This could erroneously happen in the following
272
        // cases:
273
        // 1. Unblinded node: misuses the error code.
274
        // 2. A receiving introduction node: erroneously sends the error code,
275
        //    as the spec indicates that receiving introduction nodes should
276
        //    use regular errors.
277
        //
278
        // Note that we expect the case where this error is sent from a node
279
        // after the introduction node to be handled elsewhere as this is part
280
        // of a more general class of errors where the introduction node has
281
        // failed to convert errors for the blinded route.
282
        case *lnwire.FailInvalidBlinding:
×
283
                failNode()
×
284

285
        // All other errors are considered terminal if coming from the
286
        // final hop. They indicate that something is wrong at the
287
        // recipient, so we do apply a penalty.
288
        default:
×
289
                failNode()
×
290
        }
291
}
292

293
// processPaymentOutcomeIntermediate handles failures sent by an intermediate
294
// hop.
295
func (i *interpretedResult) processPaymentOutcomeIntermediate(
296
        route *route.Route, errorSourceIdx int,
297
        failure lnwire.FailureMessage) {
3✔
298

3✔
299
        reportOutgoing := func() {
6✔
300
                i.failPair(
3✔
301
                        route, errorSourceIdx,
3✔
302
                )
3✔
303
        }
3✔
304

305
        reportOutgoingBalance := func() {
6✔
306
                i.failPairBalance(
3✔
307
                        route, errorSourceIdx,
3✔
308
                )
3✔
309

3✔
310
                // All nodes up to the failing pair must have forwarded
3✔
311
                // successfully.
3✔
312
                i.successPairRange(route, 0, errorSourceIdx-1)
3✔
313
        }
3✔
314

315
        reportIncoming := func() {
6✔
316
                // We trust ourselves. If the error comes from the first hop, we
3✔
317
                // can penalize the whole node. In that case there is no
3✔
318
                // uncertainty as to which node to blame.
3✔
319
                if errorSourceIdx == 1 {
6✔
320
                        i.failNode(route, errorSourceIdx)
3✔
321
                        return
3✔
322
                }
3✔
323

324
                // Otherwise report the incoming pair.
325
                i.failPair(
×
326
                        route, errorSourceIdx-1,
×
327
                )
×
328

×
329
                // All nodes up to the failing pair must have forwarded
×
330
                // successfully.
×
331
                if errorSourceIdx > 1 {
×
332
                        i.successPairRange(route, 0, errorSourceIdx-2)
×
333
                }
×
334
        }
335

336
        reportNode := func() {
3✔
337
                // Fail only the node that reported the failure.
×
338
                i.failNode(route, errorSourceIdx)
×
339

×
340
                // Other preceding channels in the route forwarded correctly.
×
341
                if errorSourceIdx > 1 {
×
342
                        i.successPairRange(route, 0, errorSourceIdx-2)
×
343
                }
×
344
        }
345

346
        reportAll := func() {
3✔
347
                // We trust ourselves. If the error comes from the first hop, we
×
348
                // can penalize the whole node. In that case there is no
×
349
                // uncertainty as to which node to blame.
×
350
                if errorSourceIdx == 1 {
×
351
                        i.failNode(route, errorSourceIdx)
×
352
                        return
×
353
                }
×
354

355
                // Otherwise penalize all pairs up to the error source. This
356
                // includes our own outgoing connection.
357
                i.failPairRange(
×
358
                        route, 0, errorSourceIdx-1,
×
359
                )
×
360
        }
361

362
        switch failure.(type) {
3✔
363

364
        // If a node reports onion payload corruption or an invalid version,
365
        // that node may be responsible, but it could also be that it is just
366
        // relaying a malformed htlc failure from it successor. By reporting the
367
        // outgoing channel set, we will surely hit the responsible node. At
368
        // this point, it is not possible that the node's predecessor corrupted
369
        // the onion blob. If the predecessor would have corrupted the payload,
370
        // the error source wouldn't have been able to encrypt this failure
371
        // message for us.
372
        case *lnwire.FailInvalidOnionVersion,
373
                *lnwire.FailInvalidOnionHmac,
374
                *lnwire.FailInvalidOnionKey:
3✔
375

3✔
376
                reportOutgoing()
3✔
377

378
        // If InvalidOnionPayload is received, we penalize only the reporting
379
        // node. We know the preceding hop didn't corrupt the onion, since the
380
        // reporting node is able to send the failure. We assume that we
381
        // constructed a valid onion payload and that the failure is most likely
382
        // an unknown required type or a bug in their implementation.
383
        case *lnwire.InvalidOnionPayload:
×
384
                reportNode()
×
385

386
        // If the next hop in the route wasn't known or offline, we'll only
387
        // penalize the channel set which we attempted to route over. This is
388
        // conservative, and it can handle faulty channels between nodes
389
        // properly. Additionally, this guards against routing nodes returning
390
        // errors in order to attempt to black list another node.
391
        case *lnwire.FailUnknownNextPeer:
3✔
392
                reportOutgoing()
3✔
393

394
        // Some implementations use this error when the next hop is offline, so we
395
        // do the same as FailUnknownNextPeer and also process the channel update.
396
        case *lnwire.FailChannelDisabled:
3✔
397

3✔
398
                // Set the node pair for which a channel update may be out of
3✔
399
                // date. The second chance logic uses the policyFailure field.
3✔
400
                i.policyFailure = &DirectedNodePair{
3✔
401
                        From: route.Hops[errorSourceIdx-1].PubKeyBytes,
3✔
402
                        To:   route.Hops[errorSourceIdx].PubKeyBytes,
3✔
403
                }
3✔
404

3✔
405
                reportOutgoing()
3✔
406

3✔
407
                // All nodes up to the failing pair must have forwarded
3✔
408
                // successfully.
3✔
409
                i.successPairRange(route, 0, errorSourceIdx-1)
3✔
410

411
        // If we get a permanent channel, we'll prune the channel set in both
412
        // directions and continue with the rest of the routes.
413
        case *lnwire.FailPermanentChannelFailure:
3✔
414
                reportOutgoing()
3✔
415

416
        // When an HTLC parameter is incorrect, the node sending the error may
417
        // be doing something wrong. But it could also be that its predecessor
418
        // is intentionally modifying the htlc parameters that we instructed it
419
        // via the hop payload. Therefore we penalize the incoming node pair. A
420
        // third cause of this error may be that we have an out of date channel
421
        // update. This is handled by the second chance logic up in mission
422
        // control.
423
        case *lnwire.FailAmountBelowMinimum,
424
                *lnwire.FailFeeInsufficient,
425
                *lnwire.FailIncorrectCltvExpiry:
3✔
426

3✔
427
                // Set the node pair for which a channel update may be out of
3✔
428
                // date. The second chance logic uses the policyFailure field.
3✔
429
                i.policyFailure = &DirectedNodePair{
3✔
430
                        From: route.Hops[errorSourceIdx-1].PubKeyBytes,
3✔
431
                        To:   route.Hops[errorSourceIdx].PubKeyBytes,
3✔
432
                }
3✔
433

3✔
434
                // We report incoming channel. If a second pair is granted in
3✔
435
                // mission control, this report is ignored.
3✔
436
                reportIncoming()
3✔
437

438
        // If the outgoing channel doesn't have enough capacity, we penalize.
439
        // But we penalize only in a single direction and only for amounts
440
        // greater than the attempted amount.
441
        case *lnwire.FailTemporaryChannelFailure:
3✔
442
                reportOutgoingBalance()
3✔
443

444
        // If FailExpiryTooSoon is received, there must have been some delay
445
        // along the path. We can't know which node is causing the delay, so we
446
        // penalize all of them up to the error source.
447
        //
448
        // Alternatively it could also be that we ourselves have fallen behind
449
        // somehow. We ignore that case for now.
450
        case *lnwire.FailExpiryTooSoon:
×
451
                reportAll()
×
452

453
        // We only expect to get FailInvalidBlinding from an introduction node
454
        // in a blinded route. The introduction node in a blinded route is
455
        // always responsible for reporting errors for the blinded portion of
456
        // the route (to protect the privacy of the members of the route), so
457
        // we need to be careful not to unfairly "shoot the messenger".
458
        //
459
        // The introduction node has no incentive to falsely report errors to
460
        // sabotage the blinded route because:
461
        //   1. Its ability to route this payment is strictly tied to the
462
        //      blinded route.
463
        //   2. The pubkeys in the blinded route are ephemeral, so doing so
464
        //      will have no impact on the nodes beyond the individual payment.
465
        //
466
        // Here we handle a few cases where we could unexpectedly receive this
467
        // error:
468
        // 1. Outside of a blinded route: erring node is not spec compliant.
469
        // 2. Before the introduction point: erring node is not spec compliant.
470
        //
471
        // Note that we expect the case where this error is sent from a node
472
        // after the introduction node to be handled elsewhere as this is part
473
        // of a more general class of errors where the introduction node has
474
        // failed to convert errors for the blinded route.
475
        case *lnwire.FailInvalidBlinding:
3✔
476
                introIdx, isBlinded := introductionPointIndex(route)
3✔
477

3✔
478
                // Deal with cases where a node has incorrectly returned a
3✔
479
                // blinding error:
3✔
480
                // 1. A node before the introduction point returned it.
3✔
481
                // 2. A node in a non-blinded route returned it.
3✔
482
                if errorSourceIdx < introIdx || !isBlinded {
3✔
483
                        reportNode()
×
484
                        return
×
485
                }
×
486

487
                // Otherwise, the error was at the introduction node. All
488
                // nodes up until the introduction node forwarded correctly,
489
                // so we award them as successful.
490
                if introIdx >= 1 {
6✔
491
                        i.successPairRange(route, 0, introIdx-1)
3✔
492
                }
3✔
493

494
                // If the hop after the introduction node that sent us an
495
                // error is the final recipient, then we finally fail the
496
                // payment because the receiver has generated a blinded route
497
                // that they're unable to use. We have this special case so
498
                // that we don't penalize the introduction node, and there is
499
                // no point in retrying the payment while LND only supports
500
                // one blinded route per payment.
501
                //
502
                // Note that if LND is extended to support multiple blinded
503
                // routes, this will terminate the payment without re-trying
504
                // the other routes.
505
                if introIdx == len(route.Hops)-1 {
3✔
506
                        i.finalFailureReason = &reasonError
×
507
                } else {
3✔
508
                        // If there are other hops between the recipient and
3✔
509
                        // introduction node, then we just penalize the last
3✔
510
                        // hop in the blinded route to minimize the storage of
3✔
511
                        // results for ephemeral keys.
3✔
512
                        i.failPairBalance(
3✔
513
                                route, len(route.Hops)-1,
3✔
514
                        )
3✔
515
                }
3✔
516

517
        // In all other cases, we penalize the reporting node. These are all
518
        // failures that should not happen.
519
        default:
×
520
                i.failNode(route, errorSourceIdx)
×
521
        }
522
}
523

524
// introductionPointIndex returns the index of an introduction point in a
525
// route, using the same indexing in the route that we use for errorSourceIdx
526
// (i.e., that we consider our own node to be at index zero). A boolean is
527
// returned to indicate whether the route contains a blinded portion at all.
528
func introductionPointIndex(route *route.Route) (int, bool) {
3✔
529
        for i, hop := range route.Hops {
6✔
530
                if hop.BlindingPoint != nil {
6✔
531
                        return i + 1, true
3✔
532
                }
3✔
533
        }
534

535
        return 0, false
3✔
536
}
537

538
// processPaymentOutcomeUnknown processes a payment outcome for which no failure
539
// message or source is available.
540
func (i *interpretedResult) processPaymentOutcomeUnknown(route *route.Route) {
×
541
        n := len(route.Hops)
×
542

×
543
        // If this is a direct payment, the destination must be at fault.
×
544
        if n == 1 {
×
545
                i.failNode(route, n)
×
546
                i.finalFailureReason = &reasonError
×
547
                return
×
548
        }
×
549

550
        // Otherwise penalize all channels in the route to make sure the
551
        // responsible node is at least hit too. We even penalize the connection
552
        // to our own peer, because that peer could also be responsible.
553
        i.failPairRange(route, 0, n-1)
×
554
}
555

556
// failNode marks the node indicated by idx in the route as failed. It also
557
// marks the incoming and outgoing channels of the node as failed. This function
558
// intentionally panics when the self node is failed.
559
func (i *interpretedResult) failNode(rt *route.Route, idx int) {
3✔
560
        // Mark the node as failing.
3✔
561
        i.nodeFailure = &rt.Hops[idx-1].PubKeyBytes
3✔
562

3✔
563
        // Mark the incoming connection as failed for the node. We intent to
3✔
564
        // penalize as much as we can for a node level failure, including future
3✔
565
        // outgoing traffic for this connection. The pair as it is returned by
3✔
566
        // getPair is penalized in the original and the reversed direction. Note
3✔
567
        // that this will also affect the score of the failing node's peers.
3✔
568
        // This is necessary to prevent future routes from keep going into the
3✔
569
        // same node again.
3✔
570
        incomingChannelIdx := idx - 1
3✔
571
        inPair, _ := getPair(rt, incomingChannelIdx)
3✔
572
        i.pairResults[inPair] = failPairResult(0)
3✔
573
        i.pairResults[inPair.Reverse()] = failPairResult(0)
3✔
574

3✔
575
        // If not the ultimate node, mark the outgoing connection as failed for
3✔
576
        // the node.
3✔
577
        if idx < len(rt.Hops) {
6✔
578
                outgoingChannelIdx := idx
3✔
579
                outPair, _ := getPair(rt, outgoingChannelIdx)
3✔
580
                i.pairResults[outPair] = failPairResult(0)
3✔
581
                i.pairResults[outPair.Reverse()] = failPairResult(0)
3✔
582
        }
3✔
583
}
584

585
// failPairRange marks the node pairs from node fromIdx to node toIdx as failed
586
// in both direction.
587
func (i *interpretedResult) failPairRange(
588
        rt *route.Route, fromIdx, toIdx int) {
×
589

×
590
        for idx := fromIdx; idx <= toIdx; idx++ {
×
591
                i.failPair(rt, idx)
×
592
        }
×
593
}
594

595
// failPair marks a pair as failed in both directions.
596
func (i *interpretedResult) failPair(
597
        rt *route.Route, idx int) {
3✔
598

3✔
599
        pair, _ := getPair(rt, idx)
3✔
600

3✔
601
        // Report pair in both directions without a minimum penalization amount.
3✔
602
        i.pairResults[pair] = failPairResult(0)
3✔
603
        i.pairResults[pair.Reverse()] = failPairResult(0)
3✔
604
}
3✔
605

606
// failPairBalance marks a pair as failed with a minimum penalization amount.
607
func (i *interpretedResult) failPairBalance(
608
        rt *route.Route, channelIdx int) {
3✔
609

3✔
610
        pair, amt := getPair(rt, channelIdx)
3✔
611

3✔
612
        i.pairResults[pair] = failPairResult(amt)
3✔
613
}
3✔
614

615
// successPairRange marks the node pairs from node fromIdx to node toIdx as
616
// succeeded.
617
func (i *interpretedResult) successPairRange(
618
        rt *route.Route, fromIdx, toIdx int) {
3✔
619

3✔
620
        for idx := fromIdx; idx <= toIdx; idx++ {
6✔
621
                pair, amt := getPair(rt, idx)
3✔
622

3✔
623
                i.pairResults[pair] = successPairResult(amt)
3✔
624
        }
3✔
625
}
626

627
// getPair returns a node pair from the route and the amount passed between that
628
// pair.
629
func getPair(rt *route.Route, channelIdx int) (DirectedNodePair,
630
        lnwire.MilliSatoshi) {
3✔
631

3✔
632
        nodeTo := rt.Hops[channelIdx].PubKeyBytes
3✔
633
        var (
3✔
634
                nodeFrom route.Vertex
3✔
635
                amt      lnwire.MilliSatoshi
3✔
636
        )
3✔
637

3✔
638
        if channelIdx == 0 {
6✔
639
                nodeFrom = rt.SourcePubKey
3✔
640
                amt = rt.TotalAmount
3✔
641
        } else {
6✔
642
                nodeFrom = rt.Hops[channelIdx-1].PubKeyBytes
3✔
643
                amt = rt.Hops[channelIdx-1].AmtToForward
3✔
644
        }
3✔
645

646
        pair := NewDirectedNodePair(nodeFrom, nodeTo)
3✔
647

3✔
648
        return pair, amt
3✔
649
}
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