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

lightningnetwork / lnd / 11170835610

03 Oct 2024 10:41PM UTC coverage: 49.188% (-9.6%) from 58.738%
11170835610

push

github

web-flow
Merge pull request #9154 from ziggie1984/master

multi: bump btcd version.

3 of 6 new or added lines in 6 files covered. (50.0%)

26110 existing lines in 428 files now uncovered.

97359 of 197934 relevant lines covered (49.19%)

1.04 hits per line

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

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

36
// newSuccessPairResult creates a new result struct for a success.
37
func successPairResult(successAmt lnwire.MilliSatoshi) pairResult {
2✔
38
        return pairResult{
2✔
39
                success: true,
2✔
40
                amt:     successAmt,
2✔
41
        }
2✔
42
}
2✔
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 {
2✔
81

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

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

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

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

2✔
105
        if errSourceIdx == nil {
2✔
UNCOV
106
                i.processPaymentOutcomeUnknown(rt)
×
UNCOV
107
                return
×
UNCOV
108
        }
×
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)
2✔
115
        if isBlinded && introIdx < *errSourceIdx {
2✔
UNCOV
116
                i.processPaymentOutcomeBadIntro(rt, introIdx, *errSourceIdx)
×
UNCOV
117
                return
×
UNCOV
118
        }
×
119

120
        switch *errSourceIdx {
2✔
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):
2✔
128
                i.processPaymentOutcomeFinal(rt, failure)
2✔
129

130
        // An intermediate hop failed. Interpret the outcome, update reputation
131
        // and try again.
132
        default:
2✔
133
                i.processPaymentOutcomeIntermediate(
2✔
134
                        rt, *errSourceIdx, failure,
2✔
135
                )
2✔
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,
UNCOV
145
        introIdx, errSourceIdx int) {
×
UNCOV
146

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

×
UNCOV
150
        // Other preceding channels in the route forwarded correctly. Note
×
UNCOV
151
        // that we do not assign success to the incoming link to the
×
UNCOV
152
        // introduction node because it has not handled the error correctly.
×
UNCOV
153
        if introIdx > 1 {
×
UNCOV
154
                i.successPairRange(route, 0, introIdx-2)
×
UNCOV
155
        }
×
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).
UNCOV
161
        if errSourceIdx == len(route.hops) {
×
UNCOV
162
                i.finalFailureReason = &reasonError
×
UNCOV
163
        }
×
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,
UNCOV
176
                *lnwire.FailInvalidOnionKey:
×
UNCOV
177

×
UNCOV
178
                i.failNode(rt, 1)
×
UNCOV
179

×
UNCOV
180
                // If this was a payment to a direct peer, we can stop trying.
×
UNCOV
181
                if len(rt.hops) == 1 {
×
UNCOV
182
                        i.finalFailureReason = &reasonError
×
UNCOV
183
                }
×
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.
189
        default:
2✔
190
                log.Warnf("Routing failure for local channel %v occurred",
2✔
191
                        rt.hops[0].channelID)
2✔
192
        }
193
}
194

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

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

2✔
201
        failNode := func() {
2✔
UNCOV
202
                i.failNode(route, n)
×
UNCOV
203

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

UNCOV
209
                i.finalFailureReason = &reasonError
×
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) {
2✔
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

UNCOV
262
        case *lnwire.FailMPPTimeout:
×
UNCOV
263
                // Assign all pairs a success result, as the payment reached the
×
UNCOV
264
                // destination correctly. Continue the payment process.
×
UNCOV
265
                i.successPairRange(route, 0, n-1)
×
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.
UNCOV
279
        case *lnwire.FailInvalidBlinding:
×
UNCOV
280
                failNode()
×
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.
UNCOV
285
        default:
×
UNCOV
286
                failNode()
×
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) {
2✔
296

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

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

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

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

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

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

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

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

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

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

360
        switch failure.(type) {
2✔
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,
372
                *lnwire.FailInvalidOnionKey:
2✔
373

2✔
374
                reportOutgoing()
2✔
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.
UNCOV
381
        case *lnwire.InvalidOnionPayload:
×
UNCOV
382
                reportNode()
×
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:
2✔
395

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

2✔
403
                reportOutgoing()
2✔
404

2✔
405
                // All nodes up to the failing pair must have forwarded
2✔
406
                // successfully.
2✔
407
                i.successPairRange(route, 0, errorSourceIdx-1)
2✔
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.
411
        case *lnwire.FailPermanentChannelFailure:
2✔
412
                reportOutgoing()
2✔
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:
2✔
424

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

2✔
432
                // We report incoming channel. If a second pair is granted in
2✔
433
                // mission control, this report is ignored.
2✔
434
                reportIncoming()
2✔
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:
2✔
440
                reportOutgoingBalance()
2✔
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.
UNCOV
448
        case *lnwire.FailExpiryTooSoon:
×
UNCOV
449
                reportAll()
×
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:
2✔
474
                introIdx, isBlinded := introductionPointIndex(route)
2✔
475

2✔
476
                // Deal with cases where a node has incorrectly returned a
2✔
477
                // blinding error:
2✔
478
                // 1. A node before the introduction point returned it.
2✔
479
                // 2. A node in a non-blinded route returned it.
2✔
480
                if errorSourceIdx < introIdx || !isBlinded {
2✔
UNCOV
481
                        reportNode()
×
UNCOV
482
                        return
×
UNCOV
483
                }
×
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 {
4✔
489
                        i.successPairRange(route, 0, introIdx-1)
2✔
490
                }
2✔
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 {
2✔
UNCOV
504
                        i.finalFailureReason = &reasonError
×
505
                } else {
2✔
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.
UNCOV
515
        default:
×
UNCOV
516
                i.failNode(route, errorSourceIdx)
×
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) {
2✔
525
        for i, hop := range route.hops {
4✔
526
                if hop.hasBlindingPoint {
4✔
527
                        return i + 1, true
2✔
528
                }
2✔
529
        }
530

531
        return 0, false
2✔
532
}
533

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

×
UNCOV
539
        // If this is a direct payment, the destination must be at fault.
×
UNCOV
540
        if n == 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.
UNCOV
549
        i.failPairRange(route, 0, n-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 {
2✔
555
        return &mcRoute{
2✔
556
                sourcePubKey: route.SourcePubKey,
2✔
557
                totalAmount:  route.TotalAmount,
2✔
558
                hops:         extractMCHops(route.Hops),
2✔
559
        }
2✔
560
}
2✔
561

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

570
        return mcHops
2✔
571
}
572

573
// extractMCHop extracts the Hop fields that MC actually uses from a Hop.
574
func extractMCHop(hop *route.Hop) *mcHop {
2✔
575
        return &mcHop{
2✔
576
                channelID:        hop.ChannelID,
2✔
577
                pubKeyBytes:      hop.PubKeyBytes,
2✔
578
                amtToFwd:         hop.AmtToForward,
2✔
579
                hasBlindingPoint: hop.BlindingPoint != nil,
2✔
580
                hasCustomRecords: len(hop.CustomRecords) > 0,
2✔
581
        }
2✔
582
}
2✔
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) {
2✔
606
        // Mark the node as failing.
2✔
607
        i.nodeFailure = &rt.hops[idx-1].pubKeyBytes
2✔
608

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

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

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

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

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

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

2✔
652
        i.pairResults[pair] = failPairResult(amt)
2✔
653
}
2✔
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) {
2✔
658
        for idx := fromIdx; idx <= toIdx; idx++ {
4✔
659
                pair, amt := getPair(rt, idx)
2✔
660

2✔
661
                i.pairResults[pair] = successPairResult(amt)
2✔
662
        }
2✔
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) {
2✔
669

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

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

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

2✔
686
        return pair, amt
2✔
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