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

lightningnetwork / lnd / 11216766535

07 Oct 2024 01:37PM UTC coverage: 57.817% (-1.0%) from 58.817%
11216766535

Pull #9148

github

ProofOfKeags
lnwire: remove kickoff feerate from propose/commit
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

571 of 879 new or added lines in 16 files covered. (64.96%)

23253 existing lines in 251 files now uncovered.

99022 of 171268 relevant lines covered (57.82%)

38420.67 hits per line

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

88.1
/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 {
183✔
31
        return pairResult{
183✔
32
                amt: minPenalizeAmt,
183✔
33
        }
183✔
34
}
183✔
35

36
// newSuccessPairResult creates a new result struct for a success.
37
func successPairResult(successAmt lnwire.MilliSatoshi) pairResult {
129✔
38
        return pairResult{
129✔
39
                success: true,
129✔
40
                amt:     successAmt,
129✔
41
        }
129✔
42
}
129✔
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 *mcRoute, success bool, failureSrcIdx *int,
80
        failure lnwire.FailureMessage) *interpretedResult {
97✔
81

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

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

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

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

72✔
105
        if errSourceIdx == nil {
73✔
106
                i.processPaymentOutcomeUnknown(rt)
1✔
107
                return
1✔
108
        }
1✔
109

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

120
        switch *errSourceIdx {
69✔
121

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

126
        // A failure from the final hop was received.
127
        case len(rt.hops):
9✔
128
                i.processPaymentOutcomeFinal(rt, failure)
9✔
129

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

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

2✔
147
        // We fail the introduction node for not obeying the specification.
2✔
148
        i.failNode(route, introIdx)
2✔
149

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

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

166
// processPaymentOutcomeSelf handles failures sent by ourselves.
167
func (i *interpretedResult) processPaymentOutcomeSelf(rt *mcRoute,
168
        failure lnwire.FailureMessage) {
2✔
169

2✔
170
        switch failure.(type) {
2✔
171

172
        // We receive a malformed htlc failure from our peer. We trust ourselves
173
        // to send the correct htlc, so our peer must be at fault.
174
        case *lnwire.FailInvalidOnionVersion,
175
                *lnwire.FailInvalidOnionHmac,
176
                *lnwire.FailInvalidOnionKey:
2✔
177

2✔
178
                i.failNode(rt, 1)
2✔
179

2✔
180
                // If this was a payment to a direct peer, we can stop trying.
2✔
181
                if len(rt.hops) == 1 {
3✔
182
                        i.finalFailureReason = &reasonError
1✔
183
                }
1✔
184

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

195
// processPaymentOutcomeFinal handles failures sent by the final hop.
196
func (i *interpretedResult) processPaymentOutcomeFinal(route *mcRoute,
197
        failure lnwire.FailureMessage) {
9✔
198

9✔
199
        n := len(route.hops)
9✔
200

9✔
201
        failNode := func() {
14✔
202
                i.failNode(route, n)
5✔
203

5✔
204
                // Other channels in the route forwarded correctly.
5✔
205
                if n > 1 {
9✔
206
                        i.successPairRange(route, 0, n-2)
4✔
207
                }
4✔
208

209
                i.finalFailureReason = &reasonError
5✔
210
        }
211

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

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

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

×
230
                        return
×
231
                }
×
232

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

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

242
        // We are using wrong payment hash or amount, fail the payment.
243
        case *lnwire.FailIncorrectPaymentAmount,
244
                *lnwire.FailIncorrectDetails:
2✔
245

2✔
246
                // Assign all pairs a success result, as the payment reached the
2✔
247
                // destination correctly.
2✔
248
                i.successPairRange(route, 0, n-1)
2✔
249

2✔
250
                i.finalFailureReason = &reasonIncorrectDetails
2✔
251

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

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

262
        case *lnwire.FailMPPTimeout:
2✔
263
                // Assign all pairs a success result, as the payment reached the
2✔
264
                // destination correctly. Continue the payment process.
2✔
265
                i.successPairRange(route, 0, n-1)
2✔
266

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

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

290
// processPaymentOutcomeIntermediate handles failures sent by an intermediate
291
// hop.
292
//
293
//nolint:funlen
294
func (i *interpretedResult) processPaymentOutcomeIntermediate(route *mcRoute,
295
        errorSourceIdx int, failure lnwire.FailureMessage) {
58✔
296

58✔
297
        reportOutgoing := func() {
61✔
298
                i.failPair(
3✔
299
                        route, errorSourceIdx,
3✔
300
                )
3✔
301
        }
3✔
302

303
        reportOutgoingBalance := func() {
92✔
304
                i.failPairBalance(
34✔
305
                        route, errorSourceIdx,
34✔
306
                )
34✔
307

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

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

322
                // Otherwise report the incoming pair.
323
                i.failPair(
1✔
324
                        route, errorSourceIdx-1,
1✔
325
                )
1✔
326

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

334
        reportNode := func() {
62✔
335
                // Fail only the node that reported the failure.
4✔
336
                i.failNode(route, errorSourceIdx)
4✔
337

4✔
338
                // Other preceding channels in the route forwarded correctly.
4✔
339
                if errorSourceIdx > 1 {
6✔
340
                        i.successPairRange(route, 0, errorSourceIdx-2)
2✔
341
                }
2✔
342
        }
343

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

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

360
        switch failure.(type) {
58✔
361

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

×
UNCOV
374
                reportOutgoing()
×
375

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

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

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

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

1✔
403
                reportOutgoing()
1✔
404

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

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

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

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

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

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

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

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

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

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

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

513
        // In all other cases, we penalize the reporting node. These are all
514
        // failures that should not happen.
515
        default:
1✔
516
                i.failNode(route, errorSourceIdx)
1✔
517
        }
518
}
519

520
// introductionPointIndex returns the index of an introduction point in a
521
// route, using the same indexing in the route that we use for errorSourceIdx
522
// (i.e., that we consider our own node to be at index zero). A boolean is
523
// returned to indicate whether the route contains a blinded portion at all.
524
func introductionPointIndex(route *mcRoute) (int, bool) {
77✔
525
        for i, hop := range route.hops {
253✔
526
                if hop.hasBlindingPoint {
187✔
527
                        return i + 1, true
11✔
528
                }
11✔
529
        }
530

531
        return 0, false
66✔
532
}
533

534
// processPaymentOutcomeUnknown processes a payment outcome for which no failure
535
// message or source is available.
536
func (i *interpretedResult) processPaymentOutcomeUnknown(route *mcRoute) {
1✔
537
        n := len(route.hops)
1✔
538

1✔
539
        // If this is a direct payment, the destination must be at fault.
1✔
540
        if n == 1 {
1✔
541
                i.failNode(route, n)
×
542
                i.finalFailureReason = &reasonError
×
543
                return
×
544
        }
×
545

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

552
// extractMCRoute extracts the fields required by MC from the Route struct to
553
// create the more minimal mcRoute struct.
554
func extractMCRoute(route *route.Route) *mcRoute {
68✔
555
        return &mcRoute{
68✔
556
                sourcePubKey: route.SourcePubKey,
68✔
557
                totalAmount:  route.TotalAmount,
68✔
558
                hops:         extractMCHops(route.Hops),
68✔
559
        }
68✔
560
}
68✔
561

562
// extractMCHops extracts the Hop fields that MC actually uses from a slice of
563
// Hops.
564
func extractMCHops(hops []*route.Hop) []*mcHop {
68✔
565
        mcHops := make([]*mcHop, len(hops))
68✔
566
        for i, hop := range hops {
218✔
567
                mcHops[i] = extractMCHop(hop)
150✔
568
        }
150✔
569

570
        return mcHops
68✔
571
}
572

573
// extractMCHop extracts the Hop fields that MC actually uses from a Hop.
574
func extractMCHop(hop *route.Hop) *mcHop {
150✔
575
        return &mcHop{
150✔
576
                channelID:        hop.ChannelID,
150✔
577
                pubKeyBytes:      hop.PubKeyBytes,
150✔
578
                amtToFwd:         hop.AmtToForward,
150✔
579
                hasBlindingPoint: hop.BlindingPoint != nil,
150✔
580
                hasCustomRecords: len(hop.CustomRecords) > 0,
150✔
581
        }
150✔
582
}
150✔
583

584
// mcRoute holds the bare minimum info about a payment attempt route that MC
585
// requires.
586
type mcRoute struct {
587
        sourcePubKey route.Vertex
588
        totalAmount  lnwire.MilliSatoshi
589
        hops         []*mcHop
590
}
591

592
// mcHop holds the bare minimum info about a payment attempt route hop that MC
593
// requires.
594
type mcHop struct {
595
        channelID        uint64
596
        pubKeyBytes      route.Vertex
597
        amtToFwd         lnwire.MilliSatoshi
598
        hasBlindingPoint bool
599
        hasCustomRecords bool
600
}
601

602
// failNode marks the node indicated by idx in the route as failed. It also
603
// marks the incoming and outgoing channels of the node as failed. This function
604
// intentionally panics when the self node is failed.
605
func (i *interpretedResult) failNode(rt *mcRoute, idx int) {
25✔
606
        // Mark the node as failing.
25✔
607
        i.nodeFailure = &rt.hops[idx-1].pubKeyBytes
25✔
608

25✔
609
        // Mark the incoming connection as failed for the node. We intent to
25✔
610
        // penalize as much as we can for a node level failure, including future
25✔
611
        // outgoing traffic for this connection. The pair as it is returned by
25✔
612
        // getPair is penalized in the original and the reversed direction. Note
25✔
613
        // that this will also affect the score of the failing node's peers.
25✔
614
        // This is necessary to prevent future routes from keep going into the
25✔
615
        // same node again.
25✔
616
        incomingChannelIdx := idx - 1
25✔
617
        inPair, _ := getPair(rt, incomingChannelIdx)
25✔
618
        i.pairResults[inPair] = failPairResult(0)
25✔
619
        i.pairResults[inPair.Reverse()] = failPairResult(0)
25✔
620

25✔
621
        // If not the ultimate node, mark the outgoing connection as failed for
25✔
622
        // the node.
25✔
623
        if idx < len(rt.hops) {
44✔
624
                outgoingChannelIdx := idx
19✔
625
                outPair, _ := getPair(rt, outgoingChannelIdx)
19✔
626
                i.pairResults[outPair] = failPairResult(0)
19✔
627
                i.pairResults[outPair.Reverse()] = failPairResult(0)
19✔
628
        }
19✔
629
}
630

631
// failPairRange marks the node pairs from node fromIdx to node toIdx as failed
632
// in both direction.
633
func (i *interpretedResult) failPairRange(rt *mcRoute, fromIdx, toIdx int) {
2✔
634
        for idx := fromIdx; idx <= toIdx; idx++ {
7✔
635
                i.failPair(rt, idx)
5✔
636
        }
5✔
637
}
638

639
// failPair marks a pair as failed in both directions.
640
func (i *interpretedResult) failPair(rt *mcRoute, idx int) {
9✔
641
        pair, _ := getPair(rt, idx)
9✔
642

9✔
643
        // Report pair in both directions without a minimum penalization amount.
9✔
644
        i.pairResults[pair] = failPairResult(0)
9✔
645
        i.pairResults[pair.Reverse()] = failPairResult(0)
9✔
646
}
9✔
647

648
// failPairBalance marks a pair as failed with a minimum penalization amount.
649
func (i *interpretedResult) failPairBalance(rt *mcRoute, channelIdx int) {
36✔
650
        pair, amt := getPair(rt, channelIdx)
36✔
651

36✔
652
        i.pairResults[pair] = failPairResult(amt)
36✔
653
}
36✔
654

655
// successPairRange marks the node pairs from node fromIdx to node toIdx as
656
// succeeded.
657
func (i *interpretedResult) successPairRange(rt *mcRoute, fromIdx, toIdx int) {
75✔
658
        for idx := fromIdx; idx <= toIdx; idx++ {
184✔
659
                pair, amt := getPair(rt, idx)
109✔
660

109✔
661
                i.pairResults[pair] = successPairResult(amt)
109✔
662
        }
109✔
663
}
664

665
// getPair returns a node pair from the route and the amount passed between that
666
// pair.
667
func getPair(rt *mcRoute, channelIdx int) (DirectedNodePair,
668
        lnwire.MilliSatoshi) {
198✔
669

198✔
670
        nodeTo := rt.hops[channelIdx].pubKeyBytes
198✔
671
        var (
198✔
672
                nodeFrom route.Vertex
198✔
673
                amt      lnwire.MilliSatoshi
198✔
674
        )
198✔
675

198✔
676
        if channelIdx == 0 {
293✔
677
                nodeFrom = rt.sourcePubKey
95✔
678
                amt = rt.totalAmount
95✔
679
        } else {
198✔
680
                nodeFrom = rt.hops[channelIdx-1].pubKeyBytes
103✔
681
                amt = rt.hops[channelIdx-1].amtToFwd
103✔
682
        }
103✔
683

684
        pair := NewDirectedNodePair(nodeFrom, nodeTo)
198✔
685

198✔
686
        return pair, amt
198✔
687
}
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